C#异步回调仍然在后台线程...帮助! (最好没有InvokeRequired)

时间:2010-09-04 16:26:46

标签: c# events generics delegates asynchronous

我正在编写一个非常简单的异步助手类来配合我的项目。该类的目的是允许方法在后台线程上运行。这是代码;


    internal class AsyncHelper
    {
        private readonly Stopwatch timer = new Stopwatch();
        internal event DownloadCompleteHandler OnOperationComplete;

        internal void Start(Func func, T arg)
        {
            timer.Start();
            func.BeginInvoke(Done, func);
        }

        private void Done(IAsyncResult cookie)
        {
            timer.Stop();
            var target = (Func) cookie.AsyncState;
            InvokeCompleteEventArgs(target.EndInvoke(cookie));
        }

        private void InvokeCompleteEventArgs(T result)
        {
            var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed);
            if (OnOperationComplete != null) OnOperationComplete(null, args);
        }

        #region Nested type: DownloadCompleteHandler

        internal delegate void DownloadCompleteHandler(object sender, EventArgs e);

        #endregion
    }

然后通过OnOperationComplete事件返回任务的结果。问题是,当事件被引发时,它仍然在后台线程上。即如果我尝试运行此代码(如下),我会遇到交叉线程错误;

txtOutput.AppendText(e.Result + Environment.NewLine);

请告知任何想法。

5 个答案:

答案 0 :(得分:5)

使用BackgroundWorker类。它基本上与你想要的一样。

        private BackgroundWorker _worker;

    public Form1()
    {
        InitializeComponent();
        _worker = new BackgroundWorker();
        _worker.DoWork += Worker_DoWork;
        _worker.RunWorkerCompleted += Work_Completed;
    }

    private void Work_Completed(object sender, RunWorkerCompletedEventArgs e)
    {
        txtOutput.Text = e.Result.ToString();
    }

    private void Worker_DoWork(object sender, DoWorkEventArgs e)
    {
        e.Result = "Text received from long runing operation";
    }

答案 1 :(得分:3)

我建议使用Task课而不是BackgroundWorker,但either would be greatly superior to Control.Invoke or Dispatcher.Invoke.

示例:

internal class AsyncHelper<T>
{ 
  private readonly Stopwatch timer = new Stopwatch(); 
  private readonly TaskScheduler ui;

  // This should be called from a UI thread.
  internal AsyncHelper()
  {
    this.ui = TaskScheduler.FromCurrentSynchronizationContext();
  }

  internal event DownloadCompleteHandler OnOperationComplete; 

  internal Task Start(Func<T> func)
  { 
    timer.Start();
    Task.Factory.StartNew(func).ContinueWith(this.Done, this.ui);
  }

  private void Done(Task<T> task) 
  {
    timer.Stop();
    if (task.Exception != null)
    {
      // handle error condition
    }
    else
    {
      InvokeCompleteEventArgs(task.Result); 
    }
  } 

  private void InvokeCompleteEventArgs(T result) 
  { 
    var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed); 
    if (OnOperationComplete != null) OnOperationComplete(null, args); 
  } 

  internal delegate void DownloadCompleteHandler(object sender, EventArgs e); 
} 

这与BackgroundWorker非常相似(除了你要添加一个计时器)。您可能只想考虑使用BackgroundWorker

答案 2 :(得分:0)

您需要在UI线程上调用您的事件,

的WinForms

Form1.BeginInvoke(...);

WPF

Dispatcher.BeginInvoke(...);

答案 3 :(得分:0)

使用BackgroundWorker类,你基本上是在这里重新实现它。

答案 4 :(得分:0)

除非您构建帮助类以在内部执行上下文切换,否则您将始终需要在事件处理程序中调用,因为在上面的代码中,您将在非ui线程上引发事件。

要做到这一点,你的助手需要知道如何回到ui线程。您可以将ISynchronizeInvoke传递给帮助程序,然后在完成后使用它。 Somwthing like:

ISynchronizeInvoke _sync;
internal void Start(Func func, T arg, ISynchronizeInvoke sync)
{
  timer.Start();
  func.BeginInvoke(Done, func);
  _sync = sync;
}

private void InvokeCompleteEventArgs(T result)
{
  var args = new EventArgs(result, null, AsyncMethod.GetEventByClass, timer.Elapsed);
  if (OnOperationComplete != null) 
     _sync.Invoke(OnOperationComplete, new object[]{null, args});
}

Control类实现ISynchronizeInvoke,因此您可以从调用帮助程序的thisForm传递Control指针,并具有事件处理程序委托,