I am writing a very simple asynchronous helper class to go along with my project. The purpose of the class is that it allows a method to be run on a background thread. Here is the code;
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));
开发者_StackOverflow中文版 }
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
}
The result of the task is then returned through the OnOperationComplete
event. The problem is that when the event is raised, its still on the background thread. I.e. if I try to run this code (below) I get a cross threading error;
txtOutput.AppendText(e.Result + Environment.NewLine);
Please advise any thoughts.
Use BackgroundWorker class. It essentially does the same you want.
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";
}
I recommend using the Task
class rather than BackgroundWorker
, but either would be greatly superior to Control.Invoke
or Dispatcher.Invoke
.
Example:
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);
}
This is very similar to a BackgroundWorker
, though (except you're adding a timer). You may want to consider just using BackgroundWorker
.
Use the BackgroundWorker class, you're basically reimplementing it here.
Unless you build your help class to do the context switch internally you will always need to invoke in the event handler because in your code above you are raising the event on the non-ui thread.
To do that your helper needs to know how to get back on the ui thread. You could pass a ISynchronizeInvoke to the helper and then use it when done. 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});
}
The Control
class implements ISynchronizeInvoke
so you can pass the this
pointer from the Form
or Control
that calls the helper and has the event handler delegate,
You need to invoke your event on the UI thread,
WinForms
Form1.BeginInvoke(...);
WPF
Dispatcher.BeginInvoke(...);
精彩评论