C# 异步回调仍在后台线程中…帮助!(最好不需要调用)

C# 异步回调仍在后台线程中…帮助!(最好不需要调用),c#,events,generics,delegates,asynchronous,C#,Events,Generics,Delegates,Asynchronous,我正在编写一个非常简单的异步助手类来配合我的项目。该类的用途是允许在后台线程上运行方法。这是代码 internal class AsyncHelper { private readonly Stopwatch timer = new Stopwatch(); internal event DownloadCompleteHandler OnOperationComplete; internal void Start(Func f

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


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

请给出建议。

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

WinForms

Form1.BeginInvoke(…)

WPF


Dispatcher.BeginInvoke(…)

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

使用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";
    }

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

要做到这一点,您的助手需要知道如何回到ui线程。您可以将一个文件传递给助手,然后在完成后使用它。类似于:

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
,因此您可以从调用帮助程序并具有事件处理程序委托的
表单或
控件中传递
这个
指针,我建议使用
任务
类,而不是
后台工作人员
,但是

例如:

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); 
} 
内部类AsyncHelper
{ 
私有只读秒表计时器=新秒表();
专用只读任务调度器ui;
//这应该从UI线程调用。
内部异步助手()
{
this.ui=TaskScheduler.FromCurrentSynchronizationContext();
}
内部事件下载CompleteHandler OnOperationComplete;
内部任务启动(Func Func)
{ 
timer.Start();
Task.Factory.StartNew(func.ContinueWith)(this.Done,this.ui);
}
私有void已完成(任务)
{
timer.Stop();
if(task.Exception!=null)
{
//处理错误条件
}
其他的
{
InvokeCompleteEventArgs(task.Result);
}
} 
私有void InvokeCompleteEventArgs(T结果)
{ 
var args=new EventArgs(result,null,AsyncMethod.GetEventByClass,timer.appeased);
如果(OnOperationComplete!=null)OnOperationComplete(null,args);
} 
内部委托void DownloadCompleteHandler(对象发送者,事件参数e);
} 

不过,这非常类似于
BackgroundWorker
(除了添加计时器)。您可能只想使用<代码>后台工作人员< /> > .< /p>您还可以报告进度设置:和_worker.ProgressChanged+=进度_Changed;我有点忘了。。。要实际运行worker,您必须调用_-worker.RunWorkerAsync()方法。我最终选择了BackgroundWorker,但将其全部隐藏起来并放入自定义事件中,这样就不会有人知道了。当然,假设他们不看来源。(项目是开源的这一事实是一个轻微的缺陷,但哦,好吧)你是在暗示这一点吗
OnOperationComplete。BeginInvoke
是一个过时的接口,不应使用。@Jon,不,他建议您调用可视控件上的BeginInvoke或Invoke方法。这样,您将封送调用到GUI线程,并允许您操纵GUI状态
Delegate.BeginInvoke
Control.BeginInvoke
不是一回事。你读了吗?我的“三页”包括一个完整的解决方案,包括用户界面、取消支持、正确的错误编组等。您的解决方案中没有一个包括。我喜欢这个,但不确定如何实现,您能给出使用示例吗?我已更新了示例,使其与您问题中的代码类似,显示在这种情况下如何使用它。嗯,它仍然抛出错误;跨线程操作无效:控件“txtOutput”是从创建它的线程以外的线程访问的。如注释所示,您是否在UI线程上构造了
AsynchHelper
ISynchronizeInvoke
是一个过时的接口,不应使用。您是否在.NET 2.0、3.5、4.0上?您正在创建WinForms、WPF和Silverlight应用程序吗?