Benytter man HTTP stacken fra Silverlight – herunder kald af WCF services over HTTP – sker kaldet asynkront.  Derfor skal man passe på, hvis man opdaterer brugerkontroller i sin callback funktion, da den kan blive kaldt på en anden tråd end den, der ejer brugerkontrollen.  Det er gamle nyheder.  Bruger man et pattern som f.eks. MVVM, hvor opdatering af ens view sker gennem binding til properties på en viewmodel, sker marshalling til UI tråden automatisk af Silverlight’s binding mekanisme.  I andre tilfælde er man nødt til selv at sørge for, at brugerinterfacet opdateres på den rigtige tråd f.eks. gennem Dispatcher.

Jeg har først fornylig opdaget, at callbacks fra WCF services i nogle tilfælde automatisk bliver flyttet til den kaldende tråd i Silverlight.  Det gælder også Silverlight til WP7.  Det sker i det tilfælde, hvor man benytter client proxy klasserne, der genereres, når man vælger “Add service reference” fra Visual Studio. 

Lad os sige, at jeg har en service kaldet Service1 med en metode DoWork.  Jeg har tilføjet en reference til servicen gennem VS, hvorved et namespace ServiceReference1 med en proxy klasse kaldet Service1Client er blevet oprettet.  Følgende kode er dermed fuldt lovlig:

label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();

ServiceReference1.Service1Client service = new ServiceReference1.Service1Client();
service.DoWorkCompleted += (s, args) =>
    {
        // Dette kald er fuldt lovligt, og label1 og label2 vil vise samme thread id.
        label2.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    };
service.DoWorkAsync();

Kigger man dybt nok i de genererede klasser, kan man se, at de klassen AsyncOperation benyttes til at flytte kaldet tilbage til den kaldende tråd.  Har man brug for at lave noget arbejde i sin callback fra sin service, må man undlade at bruge den genererede client proxy og f.eks. bruge ChannelFactory eller noget andet.  Forrige eksempel er funktionelt ækvivalent med nedenstående, hvor marshalling til UI tråden sker manuelt gennem brug af AsyncOperation.  Bemærk at AsyncOperation skal oprettes gennem AsyncOperationManager.CreateOperation:

AsyncOperation asyncOp;

void CallWcf()
{
    asyncOp = AsyncOperationManager.CreateOperation(1);
    ServiceReference1.IService1Channel channel 
        = new ChannelFactory<ServiceReference1.IService1Channel>("BasicHttpBinding_IService1")
        .CreateChannel();
    channel.BeginDoWork(DoWorkCallback, channel);
}

void DoWorkCallback(IAsyncResult result)
{
    ServiceReference1.IService1Channel channel = result.AsyncState as ServiceReference1.IService1Channel;
    channel.EndDoWork(result);

    asyncOp.PostOperationCompleted(new SendOrPostCallback(o =>
        {
            label2.Text = (string) o;
        }), Thread.CurrentThread.ManagedThreadId.ToString());
}

I stedet for AsyncOperation kunne man også vælge at bruge SynchronizationContext eller Dispatcher direkte.  Faktisk er AsyncOperation blot en wrapper omkring SynchronizationContext, og i Silverlight er SynchronizationContext en instans af DispatcherSynchronizationContext, som igen er en wrapper omkring Dispatcher.  Så Dispatcher er den rigtige helt her.