Background tasks being stu开发者_开发百科ff that involves network I/O, disk I/O, or other long-running tasks that may or may not take place over a network. It will often intermix with code that updates the GUI, which needs to be running on another thread, the GUI thread.
Simple meaning that when opening a Form.cs file, the source code is as easy or easier to read than before. In practice, the flow of the source code must still read sequentially with regards to the order in which the code is executed, regardless of which thread it executes on. All the supporting fabric must be reusable and hidden away somewhere, rather than being included in each Form.
Googling MSDN: found that the solution officially sanctioned by Microsoft is the System.ComponentModel.BackgroundWorker, which falls (very!) short on the second point.
(There's also an officially sanctioned Silverlight/XAML/3.5 solution model in System.Windows.Threading.Dispatcher.)
If you really don't like BackgroundWorker, you could create your own base class for background actions, as I did here.
Use BackgroundWorker for that
You can still use the BackgroundWorker
. It does not need to live as a component on the form. You can just as easily wrap it into a class that can then be reused in each of the forms.
However, this will be little different from simply setting up the worker for the background task when needed.
Can you explain why you say the BackgroundWorker
falls short?
In most cases it requires 2-3 extra lines of code.
This is the simplest idea I've come up with so far. It may be entirely un-kosher, I've coded incredibly close to zero Windows.Forms applications.
It involves a helper which has two main methods, Background() and Foreground(). Either of these take a delegate specified in the form of a lambda expression. Background() starts any given delegate on a background thread and returns immediately. Foreground() sends any given delegate "back" to the GUI thread using Form.BeginInvoke() and returns immediately.
In the following is a code example of how to use this design pattern, provided that the helper is already implemented.
public class Form1 : Form {
protected ProgressBar progressBar1;
protected Button button1;
protected BackgroundHelper helper = new BackgroundHelper();
public void button1_Click(...) {
// Execute code in the background.
helper.Background(() => {
for (int i = 0; i <= 100; i++) {
// Continually report progress to user.
helper.Foreground<int>(i, j => {
progressBar1.Value = j;
});
// Simulate doing I/O or whatever.
Thread.Sleep(25);
}
});
}
}
This keeps the code neatly sequential, provides shared variables in a good place, and allows loops that span the two threads.
To clarify what the helper does,
- The constructor starts a background thread that waits on a queue.
- Both Background() and Foreground() returns immediately.
- Background() enqueues code to run in the background thread using the internal queue.
- Foreground() does the same, with BeginInvoke on the GUI thread that first created the helper.
EDIT: Implementation:
http://code.google.com/p/backgrounder/
Following your question of Disk I/O for example, I wrote an asynchronous File-scanner class recently (or the 100th time over all the years). It's re-usable like you want, by just instantiating a new instance and hooking into the events.
I encapsulated the functionality inside a class that implements cross-thread safety. the class fires off events to notify the caller of updates and when it's complete.
You say that "the source code must still read sequentially with regards to the order in which the code is executed", but realize that threads run parallel to each other. That's why its good to separate the code structures.
This demonstrates isolating reusable code into a separate class...
Public Class FileScanner
Public Event Scan_Complete(sender As Object)
Public Event Scan_Update(sender As Object, filename As String)
Public Event Scan_Error(sender As Object, ex As Exception)
Private Delegate Sub del_ScanComplete()
Sub New(syncObject As Control, path String)
Me.SynchronizeObject = syncObject
ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf ScanFilesAsync), path)
End Sub
Private Sub ScanFilesAsync(ByVal state As Object)
' scan files here
' call the method to raise the Complete event
ScanComplete()
End Sub
Private Sub ScanComplete()
If SynchronizeObject.InvokeRequired Then
' we cant raise event on a different thread than our caller
' so lets invoke our method on the same caller thread
Dim d As New del_ScanComplete(AddressOf ScanComplete)
SynchronizeObject.Invoke(d)
Else
' no synchronize needed, tell the caller we are done
RaiseEvent Complete(Me)
End If
End Sub
End Class
An alternativ for doing background tasks in Windows Forms and raising events when they are done, is to use the AsyncOperationManager and AsyncOperation classes. To note is here that the AsyncOperation has to be created in the UI thread to work properly. Otherwise the UI code has to check for InvokeRequired.
public class BackgroundTask{
private AsyncOperation _asyncOperation;
public EventHandler Done;
public BackgroundTask(){
_asyncOperation = AsyncOperationManager.CreateOperation();
}
public void DoAsync(object userState) {
System.Threading.ThreadPool.QueueUserWorkItem( ExecuteDo, userState);
}
private void ExecuteDo(object state) {
// Do your work here
// Raise event after finish
_asyncOperation.PostOperationCompleted( Finished, EventArgs.Empty );
}
private void LookUpFinished( object eventargs ) {
OnDone( ( EventArgs) eventargs );
}
private void OnDone( LookUpEventArgs e ) {
EventHandler localEvent = Done;
if ( localEvent!= null ) {
localEvent(this,e);
}
}
}
Here´s the code how to use it:
public class MyForm : Form {
public MyForm() {
InitializeComponent();
}
protected override OnShown(EventArgs e) {
BackgroundTask task = new BackgroundTask();
task.Done += SignalTaskDone;
}
private void SignalTaskDone(object sender, EventArgs e){
MessageBox.Show(this, "Task finished");
}
}
As argued quite persuasively here, Microsoft has seen the flaws in BackgroundWorker, and you can get much better, clearer code using Task.Run with the async keyword.
精彩评论