开发者

Asynchronous WCF Services in WPF - events

开发者 https://www.devze.com 2023-04-08 16:11 出处:网络
I am using WCF services asynchronously in a WPF application. So I have class with all the web service. The view models call the method in this proc, which in-turn calls the web service.

I am using WCF services asynchronously in a WPF application. So I have class with all the web service. The view models call the method in this proc, which in-turn calls the web service.

So the view Model code looks like this:

WebServiceAgent.GetProductByID(SelectedProductID, (s, e)=>{States = e.Result;});

And the WebService agent looks like:

    public static void GetProductByID(int ProductID, EventHandler<GetProductListCompletedEventArgs> callback)
    {
        Client.GetProductByIDCompleted += callback;
        Client.GetProductByIDAsync(ProductID);
    }

Is this a good approach? I am using MVVM light too开发者_运维知识库lkit. So the View Model static, so in the lifetime of the application, the view model stays. But each time the view model calls this WebServiceAgent, I think I am registering an event. But that event is not being unregistered.

Is this a problem. Lets say the view Model is called for 20 - 30 times. I am inserting some kind of memory leak?


Some helpful information, based on the mistakes I learned from myself:

  • The Client object seems to be re-used all the time. When not unregisering event handlers, they will stack up when future invokations of the same operations finish and you'll get unpredictable results.
  • The States = e.Result statement is executed on the event handler's thread, which is not the UI dispatcher thread. When updating lists or complex properties this will cause problems.
  • In general not unregistering event handlers when they are invoked is a bad idea as it will indeed cause hard to find memory leaks.

You should probably refactor to create or re-use a clean client, wrap the viewmodel callback inside another callback that will take care of unregistering itself, cleaning up the client, and invoking the viewmodel's callback on the main dispatcher thread.

If you think all this is tedious, check out http://blogs.msdn.com/b/csharpfaq/archive/2010/10/28/async.aspx and http://msdn.microsoft.com/en-us/vstudio/async.aspx. In the next version of C# an async keyword will be introduced to make this all easier. A CTP is available already.


Event handlers are death traps and you will leak them if you do not "unsubscribe" with "-=".

One way to avoid is to use RX (Reactive Extensions) that will manage your event subscriptions. Take a look at http://msdn.microsoft.com/en-us/data/gg577609 and specifically creating Observable by using Observable.FromEvent or FromAsync http://rxwiki.wikidot.com/101samples.


This is unfortunaltely not a good approach.

I learned this the hard way in silverlight.

Your WebserviceAgent is probably a long-life object, whereas the model or view is probably short-life

Events give references, and in this case the webservice agent, and wcf client a reference to the model. A long lifeobject has a reference to a short life object, this means the short life object will not be collected, and so will have a memory leak.

As Pieter-Bias said, the async functionality will make this easier.

Have you looked at RIA services? This is the exact problem that RIA services was designed to solve


Yes, the event handlers are basically going to cause a leak unless removed. To get the near-single line equivalent of what you're expressing in your code, and to remove handlers you're going to need an instance of some sort of class that represents the full lifecycle of the call and does some housekeeping.

What I've done is create a Caller<TResult> class that uses an underlying WCF client proxy following this basic pattern:

  • create a Caller instance around an existing or new client proxy (the proxy's lifecycle is outside of the scope of the call to be made (so you can use a new short-lived one or an existing long-lived one).
  • use one of Caller's various CallAsync<TArg [,...]> overloads to specify the async method to call and the intended callback to call upon completion. This method will choose the async method that also takes a state parameter. The state parameter will be the Caller instance itself.
  • I say intended because the real handler that will be wired up will do a bit more housekeeping. The real callback is what will be called at the end of the async call, and will
    • check that ReferenceEquals(e.UserState, this) in your real handler
    • if not true, immediately return (the event was not intended to be the result of this particular call and should be ignored; this is very important if your proxy is long lived)
    • otherwise, immediately remove the real handler
    • call your intended, actual callback with e.Result
  • Modify Caller's real handler as needed to execute the intended callback on the right thread (more important for WPF than Silverlight)

The above implementation should also have separate handlers for cases where e.Error is non-null or e.Cancelled is true. This gives you the advantage of not checking these cases in your intended callback. Perhaps your overloads take in optional handlers for those cases.

At any rate, you end up cleaning up handlers aggressively at the expense of some per-call wiring. It's a bit expensive per-call, but with proper optimization ends up being far less expensive than the over-the-wire WCF call anyway.

Here's an example of a call using the class (you'll note I use method groups in many cases to increase the readability, though HandleStuff could have been result => use result ). The first method group is important, because CallAsync gets the owner of that delegate (i.e. the service instance), which is needed to call the method; alternatively the service could be passed in as a separate parameter).

Caller<AnalysisResult>.CallAsync(
    // line below could also be longLivedAnalyzer.AnalyzeSomeThingsAsync
    new AnalyzerServiceClient().AnalyzeSomeThingsAsync,
    listOfStuff,
    HandleAnalyzedStuff,
    // optional handlers for error or cancelled would go here
    onFailure:TellUserWhatWentWrong);
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号