Проблема использования AutoResetEvent

Я пытаюсь использовать объект AutoResetEvent, чтобы заблокировать поток до тех пор, пока async. загрузка веб-клиента завершена.

Моя проблема в том, что как только я вызываю WaitOne(), поток просто блокируется, и VS никогда не достигает точки останова в методе обработчика событий DownloadComplete.

Вот мой код

//Class used to pass arguments to WebClient's events...

public class RunArgs
{
    public JobInfo jobInfo;
    public int jobTotal;
    public int jobIndex;
    public AutoResetEvent AutoResetEventObject;
}

List<JobInfo> jl = ConfigSectionWrapper.GetAllJobs();

int jobAmount = jl.Count;
int jobIndex = 0;

RunArgs args = new RunArgs();
args.jobTotal = jl.Count;


foreach (JobInfo ji in jl)
{


    if (ji.enabled == "0")
    {
        args.jobIndex++;
        continue;
    }

    try
    {
        args.jobIndex++;
        args.jobInfo = ji;

        appLog.Source = ji.eventSource;
        appLog.WriteEntry(string.Format("Started job {0}...", ji.jobName),         EventLogEntryType.Information);
        ji.fullFileName = string.Format(ji.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
        ji.fullFileName = string.Format("{0}{1}", ji.downloadDirectory, ji.fullFileName);

        using (WebClient wc = new WebClient())
        {
            AutoResetEvent notifier = new AutoResetEvent(false);
            args.AutoResetEventObject = notifier;
            wc.Credentials = CredentialCache.DefaultNetworkCredentials;
            wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
            wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...
            notifier.WaitOne();
         }
    }
    catch (Exception ex)
    {
        appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
        DeleteFile(ji.fullFileName);
    }

}

private void DownloadCompleted(object sender, AsyncCompletedEventArgs e)
{

    RunArgs args = (RunArgs)e.UserState;

    //Do things....

    args.AutoResetEventObject.Set();  
 }

Поэтому я создаю экземпляр notifier со значением false в конструкторе, потому что я не хочу, чтобы о его статусе уже сообщалось. Если я неправильно читаю MSDN?

Что-то явно не так?


person Francis Ducharme    schedule 12.07.2013    source источник


Ответы (3)


WebClient использует AsyncOperationManager для управления асинхронными операциями. Итак, убедитесь, что вы знаете, как эти асинхронные операции вызываются в разных сценариях.

Под WinForms

В приложении WinForm AsyncOperationManager использует файл WindowsFormsSynchronizationContext. Как говорит @Michael, вызов WaitOne() блокирует основной поток формы от запуска события DownloadCompleted. В WinForms DownloadCompleted выполняется в основном потоке WinForm.

Итак, удалите notifier.WaitOne(), и все должно заработать. DownloadCompleted должен вызываться потоком главного окна (предположительно, тем, который вы блокируете с помощью WaitOne()).

Под консольными приложениями

В приложении типа Console AsyncOperationManager использует System.Threading.SynchronizationContext, а DownloadCompleted выполняется асинхронно потоком из пула потоков.

Нет проблем с вызовом notifier.WaitOne() в приложении Console; и приведенный выше код работает так, как ожидалось.

person Brian Chavez    schedule 12.07.2013

Я не нашел никакой документации, подтверждающей это, но глядя на код WebClient в Reflector, кажется, что события вызываются в основном потоке, а не в фоновом потоке. Поскольку ваш основной поток блокируется, когда вы вызываете notifier.WaitOne(), обработчик событий никогда не вызывается.

Если приведенный вами пример точно представляет ваш код, нет никакой необходимости использовать wc.DownloadFileAsync() вместо wc.DownloadFile(). Этот вызов notifier.WaitOne() в конечном итоге превращает это в синхронную операцию. Если вы ищете настоящую асинхронную операцию, вам придется сделать это по-другому.

person Michael Gunter    schedule 12.07.2013
comment
Да, это старый проект, над которым я работал несколько месяцев назад, и я думаю, что была причина, по которой я не использовал DownloadFile() (синхронизацию). Я думаю, что тайм-аут на нем не может быть продлен для моих нужд (я загружаю отчеты SSRS, что может занять 5-10 минут) - person Francis Ducharme; 13.07.2013
comment
Вам нужно будет переписать это. Вам нужно будет сохранить экземпляр WebClient (удалить оператор using) и вызвать для него Dispose позже (например, когда будет вызван ваш обработчик событий). И ожидайте события на основном потоке. - person Michael Gunter; 13.07.2013
comment
Итак, Брайан, значит, ты не видишь ничего плохого в коде, который я разместил? - person Francis Ducharme; 13.07.2013
comment
Это зависит от того, используете ли вы WinForms или консольное приложение; потому что AsyncOperationManager.SynchronizationContext, который использует WebClient, отличается в каждом сценарии. Код не будет сигнализировать в приложении WinForms; но ваш код будет работать в консольном приложении. - person Brian Chavez; 13.07.2013

Вот что я в итоге сделал:

private AutoResetEvent notifier = new AutoResetEvent(false);

Теперь основной цикл выглядит так:

        foreach (JobInfo ji in jl)
        {
            if (ji.enabled == "0")
            {
                args.jobIndex++;
                continue;
            }

            args.jobInfo = ji;
            Thread t = new Thread(new ParameterizedThreadStart(startDownload));
            t.Start(args);
            notifier.WaitOne();
        }

private void startDownload(object startArgs)
    {
        RunArgs args = (RunArgs)startArgs;

        try
        {
            args.jobIndex++;
            appLog.Source = args.jobInfo.eventSource;
            appLog.WriteEntry(string.Format("Started job {0}...", args.jobInfo.jobName), EventLogEntryType.Information);
            args.jobInfo.fullFileName = string.Format(args.jobInfo.reportFileName, string.Format("{0}-{1}-{2}", DateTime.Now.Year.ToString(), DateTime.Now.Month.ToString().PadLeft(2, '0'), DateTime.Now.Day.ToString().PadLeft(2, '0')));
            args.jobInfo.fullFileName = string.Format("{0}{1}", args.jobInfo.downloadDirectory, args.jobInfo.fullFileName);

            WebClient wc = new WebClient();

            wc.Credentials = CredentialCache.DefaultNetworkCredentials;
            wc.DownloadFileCompleted += new AsyncCompletedEventHandler(DownloadCompleted);
            wc.DownloadFileAsync(new Uri(args.jobInfo.reportURL), args.jobInfo.fullFileName, args); //Pass the args params to event handler...

        }
        catch (Exception ex)
        {
            appLog.WriteEntry(string.Format("Error starting report execution: {0}", ex.Message), EventLogEntryType.Error);
            DeleteFile(args.jobInfo.fullFileName);
            notifier.Set();
        }

    }

Итак, теперь AutoResetEvent блокирует основной поток, но t может успешно вызвать DownloadFileCompleteEvent. И в событии DownloadFileCompleted я, очевидно, также выполняю notifier.Set().

Спасибо всем!

person Francis Ducharme    schedule 15.07.2013