【问题标题】:AutoResetEvent use issueAutoResetEvent 使用问题
【发布时间】:2013-07-15 22:33:29
【问题描述】:

我正在尝试使用 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 实例化 notifier,因为我不希望它的状态已经发出信号。除非我读错了 MSDN?

有什么明显的错误吗?

【问题讨论】:

    标签: c# .net multithreading autoresetevent


    【解决方案1】:

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

    在 WinForms 下

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

    所以,删除notifier.WaitOne(),它应该可以工作。 DownloadCompleted 需要被主窗口线程调用(大概是你用WaitOne() 阻塞的那个)。

    在控制台应用程序下

    Console 类型的应用程序下,AsyncOperationManager 使用System.Threading.SynchronizationContext,而DownloadCompleted 由线程池中的线程异步执行。

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

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

      • 是的,这是我几个月前从事的一个旧项目,我认为我没有使用 DownloadFile()(同步)是有原因的。我认为它的超时不能扩展到我的需要(我正在下载可能需要 5-10 分钟才能完成的 SSRS 报告)
      • 你需要重写这个。您需要存储 WebClient 实例(删除 using 语句)并稍后在其上调用 Dispose(例如,当您的事件处理程序被调用时)。并期待主线程上的事件。
      • 那么,Brian,那么您发现我发布的代码没有问题吗?
      • 这取决于您使用的是 WinForms 还是控制台应用程序;因为WebClient 使用的AsyncOperationManager.SynchronizationContext 在每个场景下都是不同的。该代码将无法在 WinForms 应用程序下发出信号;但您的代码将在控制台应用程序下运行。
      【解决方案3】:

      这是我最终做的:

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

      谢谢大家!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多