Jeg brugt lidt tid på at besøge noget kode som jeg har skrevet i .NET 3.0. Tanken gik på, om ikke jeg kunne optimere applikationen med nogle af de nye parallel muligheder i .NET 4.0. Det første der sprang i øjnene var, at jeg lavede en del asynkrone kald til webservices, for at sikre responsive UI på WPF applikationen. Jeg syntes ikke de mønstre der er for asynkron programmering i .NET. er nemme at forstå, eller at læse. Har jeg ret? Se nu følgende stykke "gammel" asynkron kode:

public delegate List<DTO.MeetingRoom> GetRoomDelegate(Period p);
GetRoomDelegate myDelegate = dataaccesslayer.GetMeetingRoomsAtLocation;
myDelegate.BeginInvoke(p, new System.AsyncCallback(ResultsReturned), myDelegate);

Når så BeginInvoke returnere, så kaldes ResultsReturned:

private void ResultsReturned(IAsyncResult iar)
{
  List<MeetingRoom> result;
 
GetRoomDelegate del = (GetRoomDelegate)iar.AsyncState;
 
try
 
{
   
result = del.EndInvoke(iar);
   
this.Invoke(new System.Windows.Forms.MethodInvoker(delegate
   
{ this.uc.UpdateMeetingRooms(result);
   
}));
 
} ...

Først skal jeg have fat i GetRoomDelegaten fra AsyncState'en der initielt er ført med kaldet i BeginInvoke, så kalde EndInvoke for at få resultatet. Efterfølgende skal jeg lave nogle besværgelser for at få fat i UI-threaden. Og så er der ikke engang noget error/exception-handling kode indkluderet.

Det bliver pænere med System.Threading.Tasks. Nedenstående eksempel indeholder fejlhåndtering, cancellation og update af UI.

Først skal jeg have fat i en TaskScheduler. Ved at kalde FromCurrentSynchronizationContext, får jeg fat i UI-thread. Dvs at jeg kan bruge den context når jeg skal opdatere UI.

var ui = TaskScheduler.FromCurrentSynchronizationContext();

For at håndtere cancellation af tasks, så initierer jeg et par CancellationTokenSources. Første source (cts) bruger jeg at annulere alle tasks, på nær cancel. Anden source, bruger jeg stort set ikke, men ville kunne bruges hvis man vil annullere en cancel.

var cts = new CancellationTokenSource();
var cts2 = new CancellationTokenSource();

var ct = cts.Token;
var ct2 = cts2.Token;

Så laver jeg en ny task gennem Factory metoden. Det eneste interessante er, at jeg giver en cancellationtoken (ct) med. Det betyder at jeg kan kalde cts.Cancel(), og så få annuleret alle tasks der bruger ct token.

var getRooms = Task.Factory.StartNew(() =>
{
 
//Call a long running method  
 
var r = dal.GetMeetingRoomsAtLocation(p);
 
ct.ThrowIfCancellationRequested();
 
return r;
}, ct );

Ved kalde ct.ThrowIfCancellationRequested() ovenfor, få jeg smidt en OperationCanceledException, hvis cts.Cancel() er blevet kaldt. På den vis kan jeg afbryde en task sikkert. Jeg mangler (tror jeg nok) en getRooms."Abort()", da jeg ikke i denne situation behøver kontrol over hvordan denne task afbrydes. Problemet er at dal.GetMeetingRoomsAtLocation(p) kan være lang tid om at retunere – hvad nu hvis metoden tog 10 min, og krævede en masse CPU?

Nedenfor begynder jeg at bygge min 'kæde' af tasks op. Næste statment eksekveres, når forgående task afsluttes, men kun, når foregående task er afsluttet med en status der cancelled – deraf TaskContinuaitonOptions. Sidste parameter der er med, er ui sync context. Det betyder at denne task kører på UI-thread'en, og jeg frit kan opdatere UI. Bemærk at jeg bruger en anden cancellationtoken, da jeg ikke ønsker at nedenstående task annulleres sammen med de andre.

getRooms.ContinueWith(cancel =>
{
 
//cleanup
}, ct2, TaskContinuationOptions.OnlyOnCanceled, ui);

Når der sker en fejl, så gør jeg det samme som ved cancel, bare med TaskContinuationOptions.OnlyOnFaulted.  

getRooms.ContinueWith(error =>
{
 
//handel error, and notify user
}, ct, TaskContinuationOptions.OnlyOnFaulted, ui);

Hvis alt er gået godt, så opdater UI. Bemærk at det gennem typeinferens er udledt at update.Result er en List<MeetingRoom>.  

getRooms.ContinueWith(update =>
{
 
//update UI
 
this.msmeet.UpdateMeetingRooms(update.Result);
}, ct, TaskContinuationOptions.NotOnFaulted, ui);

Jeg syntes det er elegant og nemmere at læse. Det er let at opdatere ui ved hjælp af TaskScheduler.FromCurrentSynchronizationContext(). Ved brug af CancellationTokenSource() har jeg en god måde af afbryde tasks på, da jeg har kontrol over hvordan og hvornår de afbrydes. Gennem TaskContinuationOptions har jeg mulighed for at fortsætte en task, baseret på status af foregående task.