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应用程序吗?