• How to do asynchronous processing using delegates in C# & WPF

    Posted on March 23, 2012 by in C#, Dotnet, WPF

    We all know what delegates are. I am not going to bore you with what. If you are really interested in finding out, you could read my other blog “Delegates in C#”. When we do process heavy tasks we don’t want the user to wait and he does not want to wait. All he would be interested is the output. I am going to build a use case and will implement that using delegates.

    Prerequisites:
    For these usecases, I am using datagrid, you can download WPFToolkit from below link.

    Download WPF Toolkit

    UseCase 1:

    I want to calculate total invoice amount for all my clients and  at the same time want to find out list of active clients in the system without waiting for the previous request to complete. We would execute both tasks at once however we finally wait for all tasks to complete.

    Solution:

    Step 1: Create a new WPF solution.

    Step 2: Add a reference WPFToolKit.dll

    Step 3: Rename Window.xaml to HeavyProcess.xaml and update any references.

    Step 4: Open the XAML and add following elements.

    Step 4a: Added reference to toolkit to make use of datagrid

    xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"

    Step 4b: Added a button with a onclick event handler.

    <Button Name="btnStart" Height="25" Width="50" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Center" TextBlock.TextAlignment="Center" Click="btnStart_Click">Start</Button>

    Step 4c: In order to display the invoice amount, added a simple label and a text box to hold the amount value.

    <Label Name="lblInvoiceAmount" Grid.Row="1" Grid.Column="0">Invoice</Label>

    Step 4d: To display the list of clients and their attributes added a datagrid.

    <dg:DataGrid Name="dgClients" AutoGenerateColumns="False" Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding}">
                <dg:DataGrid.Columns>
                    <dg:DataGridTextColumn
          Binding="{Binding Path=ClientUID}" Header="ClientUID" />
                <dg:DataGridTextColumn
          Binding="{Binding Path=ClientName}" Header="ClientName" />
                <dg:DataGridTextColumn
          Binding="{Binding Path=ClientState}" Header="ClientState" Width="*"/>
                </dg:DataGrid.Columns>
            </dg:DataGrid>
    

    Step 5: If you put everything together, we have our complete design.

    <Window x:Class="AsyncProcessingApp.HeavyProcess"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"    
        Title="Window1" Height="300" Width="300">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70"/>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="30"/>
                <RowDefinition Height="23"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <Button Name="btnStart" Height="25" Width="50" Grid.Row="0" Grid.ColumnSpan="3" HorizontalAlignment="Center" TextBlock.TextAlignment="Center" Click="btnStart_Click">Start</Button>       
            <Label Name="lblInvoiceAmount" Grid.Row="1" Grid.Column="0">Invoice</Label>
            <TextBox Name="tbInvoiceAmount" Grid.Row="1" Grid.Column="1"></TextBox>
            <dg:DataGrid Name="dgClients" AutoGenerateColumns="False" Grid.Row="2" Grid.ColumnSpan="3" ItemsSource="{Binding}">
                <dg:DataGrid.Columns>
                    <dg:DataGridTextColumn
          Binding="{Binding Path=ClientUID}" Header="ClientUID" />
                <dg:DataGridTextColumn
          Binding="{Binding Path=ClientName}" Header="ClientName" />
                <dg:DataGridTextColumn
          Binding="{Binding Path=ClientState}" Header="ClientState" Width="*"/>
                </dg:DataGrid.Columns>
            </dg:DataGrid>
    
        </Grid>
    </Window>
    

    Step 6: Create  a simple delegate with no parameters and returns a dataset

    public delegate DataSet AsyncCaller();

    Step 7: Once the user clicks on the start button, we start our magic.

    Step 7a: We make an asyncronous call to fetch clients

    AsyncCaller asyncCaller = new AsyncCaller(DataManager.RetrieveClients);                       
    IAsyncResult result = asyncCaller.BeginInvoke(null, null);
    

    Step 7b: Now we can continue doing other work. In our example, it would be fetching total invoice amount. Now the important thing here is that since we are making this asynchrounous call on the same UI thread, unless we explicitly refresh the textbox, it wouldn’t show the amount value.

    tbInvoiceAmount.Text = 
                     DataManager.RetrieveInvoiceAmount().ToString();
       Refresh(tbInvoiceAmount);
    	  private static Action EmptyDelegate = delegate() { };
            public static void Refresh(UIElement uiElement)
            {
                uiElement.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Render, EmptyDelegate);
      }
    

    Step 7c: Once we are done fetching invoice amount, we are going to continue waiting for asynchronous process to complete.

    while (!result.IsCompleted)
                {
                    // Do any work you can do before waiting.
                    result.AsyncWaitHandle.WaitOne(10000, false);
                }
    

    Step 7d: Once we the call is completed, we retrieve the result and bind the resultset to the datagrid.

    result.AsyncWaitHandle.Close();
    object obj = asyncCaller.EndInvoke(result);
    LoadGrid((DataSet)obj);
    public void LoadGrid(DataSet ds)
             {
                dgClients.DataContext = ds.Tables[0].DefaultView;
    }
    

    UseCase 2:
    Same as UseCase1, but Iwe don’t want to wait for the result infinitely, instead  we would like to get out call back function to be called when the process is completed.

    Solution:
    Step 1: Design is going be same as in case of UseCase1. So repeat steps 1 – 6.

    Step 2: Once the user clicks on the start button, we start our work.

    Step 2a: we make an asyncronous call to fetch clients. While making an asynchronous call we are also passing our call back function (RetrieveAyncCallBack).

    AsyncCaller asyncCaller = new AsyncCaller(DataManager.RetrieveClients);                       
    IAsyncResult result = asyncCaller.BeginInvoke(RetrieveAyncCallBack, null);
    

    Step 2b: Now we can continue doing other stuff. In our example, fetching total invoice amount.

    tbInvoiceAmount.Text = DataManager.RetrieveInvoiceAmount().ToString();

    Step 2c:  When asychronous call is completed, call back function would be called.  As you notice while binding the grid we are doing it differently as this function is not being executed on UI thread anymore. So we need to call LoadGrid on UI thread, if not we would get “Invalid Operation” exception.

    public void RetrieveAyncCallBack(IAsyncResult result)
            {
                // Extract the delegate from the 
    System.Runtime.Remoting.Messaging.AsyncResult.
                AsyncCaller asyncCaller = 
    (AsyncCaller)((AsyncResult)result).AsyncDelegate;
                
                // Obtain the result.
                object obj = asyncCaller.EndInvoke(result);
                this.Dispatcher.Invoke(
                        System.Windows.Threading.DispatcherPriority.Normal,
                        new Action(
                            delegate()
                            {
                                LoadGrid((DataSet)obj);
                            }
                        ));
            }	
    

    DataManager.cs

    class DataManager
        {
            public static decimal RetrieveInvoiceAmount()
            {            
                return 50000;
            }
    
            public static DataSet RetrieveClients()
            {
                System.Threading.Thread.Sleep(10000);
    
                DataTable dtClients = new DataTable();
                dtClients.Columns.Add(new DataColumn("ClientName"));
                dtClients.Columns.Add(new DataColumn("ClientState"));
                dtClients.Columns.Add(new DataColumn("ClientUID"));
    
                DataRow dr = dtClients.NewRow();
                dr["ClientName"] = "Sprint";
                dr["ClientState"] = "NY";
                dr["ClientUID"] = "640";
                dtClients.Rows.Add(dr);
    
                DataSet dsClients = new DataSet();
                dsClients.Tables.Add(dtClients);
    
                return dsClients;
            }
        }
    
    Be Sociable, Share!
      Post Tagged with ,

    Written by

    Software architect with over 10 years of proven experience in designing & developing n-tier and web based software applications, for Finance, Telecommunication, Manufacturing, Internet and other Commercial industries. He believes that success depends on one's ability to integrate multiple technologies to solve a simple as well as complicated problem.

    View all articles by

    Email : [email protected]

    Leave a Reply