I'm trying to use an AutoResetEvent object to block the thread until the async. download of a WebClient is done.

My problem is that once I call WaitOne(), the thread just locks there and VS never reaches the breakpoint in the DownloadComplete event handler method.

Here's my code

//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();  
 }

So I instantiate notifier with false in the constructor, because I don't want its status to be signaled already. Unless I'm reading MSDN wrong ?

Anything obviously wrong ?

有帮助吗?

解决方案 3

Here's what I ended up doing:

private AutoResetEvent notifier = new AutoResetEvent(false);

Now the main loop looks like:

        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();
        }

    }

So now the AutoResetEvent is blocking the main thread but t can successfully trigger the DownloadFileCompleteEvent. And in the DownloadFileCompleted event, I'm obviously doing a notifier.Set() too.

Thanks all!

其他提示

WebClient uses the AsyncOperationManager to manage async operations. So, make sure you are aware of how these async operations get called under different scenarios.

Under WinForms

Under a WinForm application AsyncOperationManager uses the WindowsFormsSynchronizationContext. As @Michael says, the WaitOne() call blocks the main form thread from firing the DownloadCompleted event. In WinForms the DownloadCompleted is executed on the main WinForm thread.

So, remove the notifier.WaitOne() and it should work. DownloadCompleted needs to be called by the main window thread (presumably, the one which you're blocking with WaitOne()).

Under Console applications

Under a Console type application the AsyncOperationManager uses System.Threading.SynchronizationContext and the DownloadCompleted is executed asynchronously by a thread form the threadpool.

There is no issue with calling notifier.WaitOne() under a Console app; and the code above works as expected.

I haven't found any documentation to support this, but looking at the WebClient code in Reflector, it appears that the events are raised on the main thread, not the background thread. Since your main thread is blocking when you call notifier.WaitOne(), the event handler never gets called.

If the example you provided accurately represents your code, there's absolutely no need to use wc.DownloadFileAsync() instead of wc.DownloadFile(). That notifier.WaitOne() call ultimately makes this into a synchronous operation. If you're looking for a true asynchronous operation, you'll have to do this differently.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top