Click here to Skip to main content
15,881,938 members
Articles / Programming Languages / C#
Tip/Trick

Simple .NET 4.0 Asynchronous Example

Rate me:
Please Sign up or sign in to vote.
4.00/5 (3 votes)
9 Jan 2016CPOL4 min read 11.9K   190   9  
A simple WinForms example that shows one way to implement asynchronous operations in your applications

Introduction

I recently began contributing to a WinForms project that is used to acquire data from a hardware module in the host PC. The application is primarily used to configure and command the acquisition module, then manage the resulting data. The application included a plot panel that would show a sample of data recorded from one of the module channels based on a user selection. When the application started or a new channel was selected, the application would freeze while new data was acquired. I had done some work with Asynchronous programming on another .NET project and this seemed like a good place to apply that pattern.

Background

The project in question was restricted to .NET 4.0 so I wasn't able to use the new and improved async features in .NET 4.5. To summarize my design intent, when the app starts or a new channel is selected, I want the application to:

  • Acquire a sample of data from the selected channel
  • Refresh the UI with this data
  • Repeat until a new channel is selected or the user commands a different mode

Since each acquisition might take several seconds, doing this synchronously is not an option. The UI would always be waiting for the next acquisition, i.e. the main thread would be blocked.

I can't share the original project here so I made a simple WinForms app to demonstrate the pattern.

The Code

Step by step:

  1. Create a new Winforms project in Visual Studio.
  2. Add these controls to Form1: ComboBox, TextBox and a Button. I added a few labels to explain what is going on.
  3. For the combo box, I entered a few choices to the items collection that are akin to the channel selections mentioned above. [Orange, Apple, Grape, Fig, ...]

  4. In the Form1 code, you will need these usings. I don't think you need any additional project references as these are part of the core framework.
    C#
    using System.Runtime.Remoting.Messaging;
    using System.Threading;
  5. You will need a couple of delegates and fields which I will explain later:
    C#
    public Form1()
    {
       InitializeComponent();
    }
    
    IAsyncResult myAsyncResult;
    CancellationTokenSource cTokenSource = new CancellationTokenSource();
    
    delegate void AsyncMethod1Caller(string fruit, CancellationToken cts);
    delegate void SetTextCallback(string text);
    
  6. The method that we want to run asynchronously is as follows:
    C#
    void Method1(string fruit, CancellationToken cts)
               {
                   Random rnd = new Random();
                   string[] words = {"pie", "fritters", "split", "jam", "marmalade", "leaf" };
    
                   while (!cts.IsCancellationRequested)
                   {
                   // proxy for a time consuming operation
                   this.SetText(fruit + " " + words[rnd.Next(0, words.Count() - 1)]);
                   Thread.Sleep(1000);
                   }
                   return;
               }
    

    In my original project, this is where I would be acquiring data from the module. This method is pretty standard with a couple of small differences. Notice the CancellationToken in the methods arguments. This is the mechanism that the UI uses to stop the "worker" thread when it needs to. This doesn't kill the thread abruptly which is considered risky. Instead, it allows the worker to finish what it is doing and exit gracefully. The other twist is the SetText() call which is used to safely set the text box's value from the worker thread as explained in this article. This call uses one of the delegates shown above.

  7. Here is the SetText method:
    C#
    private void SetText(string text)
    {
        // Thread Safe way to set control values from another thread.
        if (this.textBox1.InvokeRequired)
        {
            SetTextCallback d = new SetTextCallback(SetText);
            this.Invoke(d, new object[] { text });
        }
        else
        {
            this.textBox1.Text = text;
        }
    }
  8. Now for the event handlers. This handler fires whenever the user selects an item from the combo box list. The first line changes our CancellationTokenSource IsCancellationRequested property to true. I then Dispose of the CancellationTokenSource and wait for the worker process to stop. In my application, I know how long each acquisition cycle is so I know how long I have to wait. Then, I recreate the CancellationTokenSource, instantiate an AsyncMethod1Caller delegate and use it to invoke Method1.
    C#
    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        cTokenSource.Cancel();  // to stop the worker thread (if any)
        cTokenSource.Dispose();
        Thread.Sleep(500);  // wait for the worker to stop
        cTokenSource = new CancellationTokenSource();
    
        AsyncMethod1Caller caller = new AsyncMethod1Caller(Method1);
        var cToken = cTokenSource.Token;
        myAsyncResult = caller.BeginInvoke(comboBox1.Text, cToken, new AsyncCallback(Method1Callback), "info");
    }

Dissecting the last (and most important) statement: I'm using the BeginInvoke method of the AsyncMethod1Caller delegate. The arguments are the selected text from the combo box (the input to the worker method), a cancelation token, a reference to an AsyncCallback and a state object that I don't do anything with. The arguments here must be the same as the signature for Method1 plus the two added arguments that are used to manage the worker thread.

Using the AsyncCallback option is important in my case because I don't want the main UI thread to block while waiting for the worker thread to complete. The callback:

C#
void Method1Callback(IAsyncResult ar)
{
    AsyncResult result = (AsyncResult)ar;
    AsyncMethod1Caller caller = (AsyncMethod1Caller)result.AsyncDelegate;
    caller.EndInvoke(ar);
}

This runs on yet another thread (from the thread pool) when the worker thread completes. It is important to call EndInvoke on the delegate we created to clean up from our little threading adventure, and be ready to launch the next worker.

Finally, I included a handler for the button that also can cancel the worker process.

C#
private void button1_Click(object sender, EventArgs e)
{
    cTokenSource.Cancel();
}

Running the Example

If you build and run it, you should get something like this:

If you pick a fruit from the combo box, the worker process will join it with another word randomly picked from a list, pause, then pick another random word, etc. Some of the combos are gastronomically weird but that's not the point. Notice you can keep picking different items from the first list, i.e., the asynchronous worker process is not interfering with the main UI thread.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
Developing Silverlight applications and other C# tools. Experience with other high level languages such as VB and Python. 3+ years with C#.

Comments and Discussions

 
-- There are no messages in this forum --