Asynchronous Actions

Prerequisites

  1. Obtain and Build the Code
  2. Setting Up a Project
  3. Master Action Basics

Referenced Assemblies

  • Caliburn.Core
  • Caliburn.PresentationFramework
  • Microsoft.Practices.ServiceLocation

Minimum Configuration

Note: Configuration should be placed in the App.xaml.cs constructor for WPF or the App.xaml.cs Application_Startup for Silverlight.

CaliburnFramework
    .ConfigureCore()
    .WithPresentationFramework()
    .Start();
Note: As an alternative to manual configuration, you can inherit your application from CaliburnApplication.

Using Asynchronous Actions (Based on Samples - AsyncActions)

Let's dive right in and see a basic usage of asynchronous actions.
  • In your project, create a new class named Calculator. Use the code below:
public class Calculator
{
    public bool CanDivide(double left, double right)
    {
        return right != 0;
    }
 
    [Preview("CanDivide")]
    [AsyncAction(BlockInteraction = true)]
    public double Divide(double left, double right)
    {
        Thread.Sleep(3000);
        return left / right;
    }
}
  • Next, use the following markup to implement your main Page (for Silverlight) or your Window (for WPF). The markup is the same with the exception that WPF could use a single xmlnss:

WPF and Silverlight
<UserControl x:Class="AsyncActions.Page"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ca="clr-namespace:Caliburn.Actions;assembly=Caliburn.Actions"
             xmlns:local="clr-namespace:AsyncActions"
             xmlns:cm="clr-namespace:Caliburn.RoutedUIMessaging;assembly=Caliburn.RoutedUIMessaging"
             Width="400"
             Height="300">
    <ca:Action.Target>
        <local:Calculator />
    </ca:Action.Target>
    <StackPanel>
        <Grid Margin="10">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>
 
            <TextBox x:Name="left"
                     Grid.Column="0" />
            <TextBlock Text="/"
                       Margin="10 0"
                       Grid.Column="1" />
            <TextBox x:Name="right"
                     Grid.Column="2" />
            <Border BorderBrush="Black"
                    BorderThickness="0 0 0 1"
                    Margin="10 0 0 0"
                    Grid.Column="3">
                <TextBlock x:Name="DivideResult" />
            </Border>
        </Grid>
 
        <Button Content="Divide"
                cm:Message.Attach="Divide(left.Text, right.Text) : DivideResult.Text" />
 
    </StackPanel>
</UserControl>

Note: This sample instantiates the Calculator instance inline. I generally prefer not to have the view control instantiation. This is for demo purposes only.

Now, run the application, put some numbers into the TextBoxes and click the button. The button should disable during the async operation, but the rest of the UI should remain responsive while the action executes on a background thread. When the operation completes, the button will enable itself and the result will be displayed in the TextBlock. The UI markup contains no knew concepts. It is exactly the same as described in action basics. The real power is in this [AsyncAction(BlockInteraction = true)] which decorates the action itself. By applying the AsyncActionAttribute, a developer can tell Caliburn that the action should be executed on a background thread. Optionally, you can set the BlockInteraction to true to cause the trigger that initiates the action to be unavailable until the async operation completes.

Note: The Preview filter is always executed synchronously on the UI thread before the async action is invoked. The result of the async action is automatically marshalled back to the UI thread for proper databinding.
  • Let's add a callback to the asynchronous action by changing our Calculator class to match the following:

public class Calculator
{
    public bool CanDivide(double left, double right)
    {
        return right != 0;
    }
 
    [Preview("CanDivide")]
    [AsyncAction(Callback = "DivideComplete", BlockInteraction = true)]
    public double Divide(double left, double right)
    {
        Thread.Sleep(3000);
        return left / right;
    }
 
    public double DivideComplete(double result)
    {
        return result * 100;
    }
}


Run the application again and notice that everything behaves the same, except that the result is multiplied by 100 before it is bound to the UI. When we add a Callback to the AsyncActionAttribute, Calibiburn takes the result of the async action and passes it as a parameter to the Callback, which is automatically called on the UI thread. If the Callback has a return value, it will be bound to the UI.

Last edited Jan 6, 2011 at 4:10 AM by EisenbergEffect, version 15