我正在编写一个非常简单的异步助手类来配合我的项目。该类的目的是允许方法在后台线程上运行。这是代码;
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);
请告知任何想法。
答案 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
,因此您可以从调用帮助程序的this
或Form
传递Control
指针,并具有事件处理程序委托,