Threading–Task Library

Introduction

Photon’s task library is designed to take the the pain out of working with asynchronous code in Silverlight. The fact the API is similar to the .Net 4 task library is actually mostly coincidence (its a natural conclusion when you spend enough time on the problem). In many respects you could consider the Photon task library to be a “System.Threading.Tasks” Light, supporting only the most common day to day features that crop up in Silverlight applications. As Silverlight 5 is likely to include Tasks to support the new async language features I don’t expect this part of the framework to live long (I wish I’d released it years ago), however, if you do decide to use it, you shouldn’t have much difficulty migrating in the future.

So what is a Task?

A task is a common abstraction on an asynchronous unit of work. In Photon there are two basic types of task, ITask which represents an asynchronous operation that does not return a value, and ITask<TResult>>, which is a task that does return a value. Tasks are very flexible, supporting a wide variety of asynchronous patterns. They can be waited on individually or collectively, they can also be organised into workflows that containing sequences, forks and joins. You can also use tasks to leverage existing code by using one of the many factory methods Photon provides for adapting common .NET asynchronous patterns into a tasks.

Some Examples

There are not enough hours in the day to go into detail on how tasks have been implemented in Photon, how they can be extended, or how they were tested using the excellent Microsoft CHESS (Still no support for VS2010 guys, thanks for that!!). But here are some example:

Adapting the Asynchronous BeginInvoke,EndInvoke pattern.

The example below shows a view model that utilised tasks and task continuations to download content asynchronously. The WebRequest and WebResponse objects implement common BeginInvoke, EndInvoke asynchronous patterns which are adapted into tasks.

public class AsyncBeginEndExampleViewModel : ModelBase
{
private bool _canDownloadUrl;
private string _downloadContent;


public AsyncBeginEndExampleViewModel()
{
this.CanDownloadUrl = true;
}


public string DownloadContent
{
get { return _downloadContent; }
set { SetProperty(ref _downloadContent, value, () => DownloadContent); }
}


public bool CanDownloadUrl
{
get { return this._canDownloadUrl; }
set { SetProperty(ref this._canDownloadUrl, value, "CanDownloadUrl"); }
}


public void DownloadUrl([Required] string url)
{
if (!this.CanDownloadUrl)
{
return;
}

// initialize the request
var request = WebRequest.Create(url);
this.CanDownloadUrl = false;

// start task to get the response
var getResponseTask = Task.Factory.StartNew<WebResponse>(
request.BeginGetResponse, request.EndGetResponse);

// continue with a task to read the response
var readResponseTask = getResponseTask.ContinueWith(
t => ReadStreamAsText(t.Result.GetResponseStream()), Task.Factory);

// sync results back to the UI thread (TaskFactories.UI)
readResponseTask.ContinueWith(
t =>
{
this.CanDownloadUrl = true;
try
{
DownloadContent = t.Result;
}
catch (Exception e)
{
MessageBox.Show("Error downloading url " + url + "\r\n" + e.ToString());
}
}, TaskFactories.UI);
}


private static string ReadStreamAsText(Stream stream)
{
using (stream)
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
}

Adapting the Asynchronous Event pattern.

The following example shows a view model making an asynchronous call a generated WCF service proxy called PersonServiceClient.  The PersonServiceClient implements the async event pattern, which defines a OperationAsync start method, and OperationCompleted event pair which are adapted into a task.

public interface IPersonServiceClient
{
void SaveAsync(Person person, object userState);
event System.EventHandler<SaveCompletedEventArgs> SaveCompleted;
}

public class AsyncEventExampleViewModel : ModelBase
{
private readonly PersonServiceClient _personServicesClient;
private bool _isSaving;


public AsyncEventExampleViewModel()
{
_personServicesClient = new PersonServiceClient();
}

public void Save(Person person)
{
if (!CanSave(person))
{
return;
}

TaskFactories.UI.FromAsyncEvent(this._personServicesClient.SaveAsync, person,
(SaveCompletedEventArgs args) => args.Result).Start(t =>
{
IsSaving = false;
if (t.Error != null)
{
// OK, not the best error handling, but we can cover that in another sample
MessageBox.Show("An error has occured.");
}
});
}

// IsSaving uses SetProperty; therefore CanSave will be re-evaluated automatically
public bool CanSave(Person person)
{
return person != null && !this.IsSaving;
}

protected bool IsSaving
{
get { return this._isSaving; }
set { this.SetProperty(ref this._isSaving, value, () => IsSaving); }
}
}


Adapting the Asynchronous Yield Pattern

The Asynchronous Yield Pattern allows a method that returns an Enumerable<ITask>  to be adapted into a single composite task. It utilises the fact that .NET will automatically compile a state machine for methods that return IEnumerable<T> making it a fairly simple task to create complex asynchronous workflows.

public class WorkFlow
{
public void PerformAsyncWorkflow()
{
var context = new YieldPatternContext<int>();
var task = Task.Factory.FromYield(AsyncWorkflow, context);
task.Start();
var result = task.Result;
// do something with result....
}


private IEnumerable<ITask> AsyncWorkflow(YieldPatternContext<int> context)
{
var task1 = Task.Factory.From<int>(SomeTask1);
yield return task1;

// if there was an error try to recover
if (task1.Error == null)
{
var recoveryTask = Task.Factory.From<int>(SomeTask1Recovery);
yield return recoveryTask;

// set the result returned from SomeTask1Recovery, note: exceptions will get propagated automatically
context.Result = recoveryTask.Result;
}
else
{
var secondTask = Task.Factory.From<int>(SomeTask2);
yield return secondTask;

// set the result return from SomeTask2
context.Result = secondTask.Result;
}
}
}

Waiting for Tasks

The example below shows how to use a task as a future:

public void PerformLongRunningTask()
{
var task = Task.Factory.From<int, int>(LongRunningTask, 1000);
task.Start();
try
{
// Result will block until the task is complete.
MessageBox.Show(task.Result.ToString());
}
catch (Exception e)
{
MessageBox.Show("The long running task had an error.");
}
}

public int LongRunningTask(int time)
{
// please no whining about how bad thread sleep is, you know what I mean!!!
Thread.Sleep(time);
return 0;
}

The following example shows how to wait for multiple tasks:

private void ContinueWhenAll(object sender, RoutedEventArgs e)
{
var task1 = Task.Factory.From<int, int>(LongRunningTask, 20);
var task2 = Task.Factory.From<int, int>(LongRunningTask, 10);
task1.Start();
task2.Start();
TaskFactories.UI.ContinueWhenAll(new ITask[] { task1, task2 }, tasks =>
{
MessageBox.Show("Tasks are complete.");
});
}

Factories

Task Factories in Photon are currently pretty basic. There only purpose being to setup how tasks are scheduled. The example below shows how to setup a factory that synchronizes dispatching of outgoing calls and  results on the UI thread (most likely what you will want in Silverlight). Note: the default factory uses the thread pool to send and receive.

public static class TaskFactories
{
public static readonly TaskFactory UI = new TaskFactory(TaskScheduler.ForUISendAndReceive());
}

Last edited Feb 4, 2011 at 8:46 PM by suedama1756, version 2

Comments

No comments yet.