The problem
When you create winform applications, doing tasks in the background is essential to avoid user frustation. Unfortunatly, it could make you write a lot more code. Here are some utility methods that reduce the complexity of asynchronous calls within windows forms.
Within you base class
You probably have a common base class for all your UI components. Add these methods :
namespace TestWinForm
{
public class BaseForm : Form
{
// Execute some code in async mode.
// When it's done, it calls the nextStep delegate, eventually with
// an exception catched during the main action.
protected virtual void Async(Action action, Action nextStep)
{
new Thread(delegate()
{
Exception exception = null;
try
{
action();
}
catch (Exception ex)
{
exception = ex;
}
ThreadSafe(() => { nextStep(exception ); });
}).Start();
}
// This allows a sub class to easily run a method within
// an UI thread without the need of creating multiple
// delegate signatures for each method signatures
protected virtual void ThreadSafe(MethodInvoker method)
{
if (InvokeRequired)
Invoke(method);
else
method();
}
}
}
Within your UI classes
Now the only thing you need to do is to encapsulate the methode content with the Async() method :
namespace TestWinForm
{
public partial class MainForm : BaseForm
{
public MainForm()
{
InitializeComponent();
}
// Here is the async trick :
// - UI will NOT freeze,
// - you can add beautifull animated gifs
private void button1_Click(object sender, EventArgs e)
{
DateTime? result = null;
Async(
() =>
{
result = GetComplexDate();
},
(ex) =>
{
if (ex == null)
textBox1.Text = result.Value.ToShortDateString();
else
textBox1.Text = ex.Message;
}
);
}
// This is the slow, data-intensive task :p
private DateTime? GetComplexDate()
{
Thread.Sleep(3000);
return DateTime.Now;
}
}
}
The traditional way
Just in case you didn’t get it. This is what you should NOT DO:
// This was traditional way :
// - UI will freeze until during 3 second ...
// - you users will complain
// avoid this !
private void button1_Click2(object sender, EventArgs e)
{
try
{
textBox1.Text = GetComplexDate().ToShortDateString();
}
catch (Exception ex)
{
textBox1.Text = ex.Message;
}
}
Happy coding !
How could this be modified to allow the user to automatically cancel the previous async call and start a new async call (with new params) by clicking the button again?
Hi Mark,
This could be achieved with the following : containing all current calls
– create a static dictionary
– AsyncCall is a class containing :
* the caller (a Control eg: the button)
* the thread that executes the code
In the Async() method, add a new instance of call at the begining and remove it at the end.
If there is already a call with the same caller, kill its thread, remove it from the collection and do a normal call.
Was it clear enough ?
Copy paste your code from BaseForm, I got error said : Delegate ‘Action’ does not take 1 arguments.