WPF/WCF异步服务调用和SynchronizationContext [英] WPF/WCF Async Service Call and SynchronizationContext

查看:311
本文介绍了WPF/WCF异步服务调用和SynchronizationContext的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我觉得必须有一个比我想出的更好的解决方案;问题出在这里:

I have a feeling there's got to be a better solution than what I've come up with; here's the problem:

WPF表单将调用WCF方法,该方法返回布尔值.调用本身不应在UI线程上,并且调用结果将需要显示在表单上,​​因此应将返回结果编组回UI线程.

A WPF form will call a WCF method, which returns a bool. The call itself should not be on the UI thread, and the result of the call will need to be displayed on the form, so the return should be marshaled back on to the UI thread.

在此示例中,我创建了一个"ServiceGateway"类,该表单将向其传递一个方法,该方法将在完成Login操作后执行.网关应使用UI SynchronizationContext调用此登录完成的委托,该方法在网关从表单实例化时传递. Login方法使用anon调用_proxy.Login的调用.异步委托,然后提供一个回调,该回调使用UI SynchronizationContext调用从窗体提供给网关的委托(回调"参数):

In this example I created a "ServiceGateway" class, to which the form will pass a method to be executed upon completion of a Login operation. The gateway should invoke this Login-complete delegate using the UI SynchronizationContext, which is passed upon instantiation of the gateway from the form. The Login method invokes a call to _proxy.Login using an anon. async delegate, and then provides a callback which invokes the delegate ('callback' param) provided to the gateway (from the form) using the UI SynchronizationContext:

[CallbackBehavior(UseSynchronizationContext = false)]
public class ChatServiceGateway : MessagingServiceCallback
{

    private MessagingServiceClient _proxy;
    private SynchronizationContext _uiSyncContext;

    public ChatServiceGateway(SynchronizationContext UISyncContext)
    {
        _proxy = new MessagingServiceClient(new InstanceContext(this));
        _proxy.Open();
        _uiSyncContext = UISyncContext;
    }


    public void Login(String UserName, Action<bool> callback)
    {
        new Func<bool>(() => _proxy.Login(UserName)).BeginInvoke(delegate(IAsyncResult result)
        {
            bool LoginResult = ((Func<bool>)((AsyncResult)result).AsyncDelegate).EndInvoke(result);
            _uiSyncContext.Send(new SendOrPostCallback(obj => callback(LoginResult)), null);
        }, null);

    }

响应按钮单击事件,从表单中调用Login方法.

The Login method is called from the form in response to a button click event.

这很好,但是我怀疑我要以错误的方式使用Login方法;尤其是因为我必须对WCF服务的其他任何方法调用做同样的事情,而且丑陋.

This works fine, but I have a suspicion I'm going about the Login method the wrong way; especially because I'll have to do the same for any other method call to the WCF service, and its ugly.

我想将异步行为和ui同步封装在网关中.在WCF端实现异步行为会更好吗?基本上,我很感兴趣是否可以为其他方法更通用地实现上述代码,或者是否有更好的方法.

I would like to keep the async behavior and ui synchronization encapsulated in the gateway. Would it be better to have the asynchronous behavior implemented on the WCF side? Basically I'm interested if I can implement the above code more generically for other methods, or if there's a better way all together.

推荐答案

async/await是您的目标至少是VS 2012和.NET 4.5.请注意,缺少SynchronizationContext引用-在await之前将其捕获,并在异步操作完成后重新发布.

Provided that you're targeting at least VS 2012 and .NET 4.5, async/await is the way to go. Note the lack of SynchronizationContext reference - it's captured under the covers before the await, and posted back to once the async operation has completed.

public async Task Login(string userName, Action<bool> callback)
{
    // The delegate passed to `Task.Run` is executed on a ThreadPool thread.
    bool loginResult = await Task.Run(() => _proxy.Login(userName));

    // OR
    // await _proxy.LoginAsync(UserName);
    // if you have an async WCF contract.

    // The callback is executed on the thread which called Login.
    callback(loginResult);
}

Task.Run主要用于将CPU绑定的工作推送到线程池,因此上面的示例确实滥用了它,但是如果您不想重写MessagingServiceClient实现的协定以使用异步的方法,这仍然是一个不错的方法.

Task.Run is primarily used to push CPU-bound work to the thread pool, so the example above does abuse it somewhat, but if you don't want to rewrite the contract implemented by MessagingServiceClient to use asynchronous Task-based methods, it is still a pretty good way to go.

或.NET 4.0方式(不支持async/await):

Or the .NET 4.0 way (no async/await support):

public Task Login(string userName, Action<bool> callback)
{
    // The delegate passed to `Task.Factory.StartNew`
    // is executed on a ThreadPool thread.
    var task = Task.Factory.StartNew(() => _proxy.Login(userName));

    // The callback is executed on the thread which called Login.
    var continuation = task.ContinueWith(
        t => callback(t.Result),
        TaskScheduler.FromCurrentSynchronizationContext()
    );

    return continuation;
}

这与您当前的处理方式有所不同,因为调用方有责任确保Login在用户界面线程上被调用希望在其上执行回调.但是,这是关于async的标准实践,并且在可以保留对UI SynchronizationContextTaskScheduler的引用,作为对ChatServiceGateway的一部分.强制在正确的线程上执行回调/继续操作,这会破坏您的实现并亲自执行(这只是我的观点)我会说这有点代码味道

This is a bit of a departure from the way you're currently doing things in that it is the responsibility of the caller to ensure that Login is getting called on the UI thread if they want the callback to be executed on it. That is, however, standard practice when it comes to async, and while you could keep a reference to the UI SynchronizationContext or TaskScheduler as part of your ChatServiceGateway to force the callback/continuation to execute on the right thread, it will blow out your implementation and personally (and this is just my opinion) I would say that that's a bit of a code smell.

这篇关于WPF/WCF异步服务调用和SynchronizationContext的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆