0

我正在尝试使用 AutoResetEvent 对象来阻止线程直到异步。WebClient 的下载完成。

我的问题是,一旦我调用 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();  
 }

所以我在构造函数中用 false 实例化通知器,因为我不希望它的状态已经发出信号。除非我读错了 MSDN?

有什么明显的错误吗?

4

3 回答 3

2

WebClient使用AsyncOperationManager来管理异步操作。因此,请确保您了解这些异步操作在不同场景下是如何被调用的。

在 WinForms 下

WinForm应用程序下AsyncOperationManager使用WindowsFormsSynchronizationContext. 正如@Michael 所说,WaitOne()调用会阻止主窗体线程触发DownloadCompleted事件。在WinForms主线程DownloadCompleted上执行。WinForm

因此,删除notifier.WaitOne()它应该可以工作。DownloadCompleted需要由主窗口线程调用(大概是您阻塞的线程WaitOne())。

在控制台应用程序下

Console类型应用程序下,AsyncOperationManager使用System.Threading.SynchronizationContextDownloadCompleted线程池由线程异步执行。

在appnotifier.WaitOne()下调用没有问题;Console并且上面的代码按预期工作。

于 2013-07-12T20:54:50.360 回答
1

我还没有找到任何支持这一点的文档,但是查看 Reflector 中的 WebClient 代码,似乎事件是在主线程上引发的,而不是在后台线程上引发的。由于您的主线程在您调用时被阻塞notifier.WaitOne(),因此事件处理程序永远不会被调用。

如果您提供的示例准确地代表了您的代码,则绝对不需要使用wc.DownloadFileAsync()代替wc.DownloadFile(). 该notifier.WaitOne()调用最终使其成为同步操作。如果您正在寻找真正的异步操作,则必须以不同的方式执行此操作。

于 2013-07-12T20:14:06.050 回答
0

这就是我最终做的事情:

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正在阻塞主线程,但可以成功触发DownloadFileCompleteEvent。在DownloadFileCompleted事件中,我显然也在做一个notifier.Set()

谢谢大家!

于 2013-07-15T14:33:29.977 回答