WPF / WCF异步服务调用和SynchronizationContext

时间:2014-09-30 20:08:18

标签: c# wpf multithreading wcf

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

WPF表单将调用WCF方法,该方法返回bool。调用本身不应该在UI线程上,并且调用的结果将需要显示在表单上,​​因此返回应该被封送回UI线程。

在这个例子中,我创建了一个“ServiceGateway”类,表单将传递一个在完成Login操作时要执行的方法。网关应使用UI SynchronizationContext调用此Login-complete委托,UI SynchronizationContext在从表单实例化网关时传递。 Login方法使用anon调用_proxy.Login调用。异步委托,然后使用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方法以响应按钮单击事件。

这很好用,但我怀疑我正在以错误的方式使用Login方法;特别是因为我必须对WCF服务的任何其他方法调用做同样的事情,而且它很难看。

我想保持封装在网关中的异步行为和ui同步。在WCF端实现异步行为会更好吗?基本上我很感兴趣,如果我可以更一般地为其他方法实现上面的代码,或者如果有更好的方法在一起。

1 个答案:

答案 0 :(得分:1)

如果您至少定位VS 2012和.NET 4.5,async/await即可。请注意缺少SynchronizationContext引用 - 它在await之前的封面下捕获,并在异步操作完成后发回。

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绑定工作推送到线程池,因此上面的示例确实有点滥用它,但如果您不想重写{{1}实现的合同使用基于异步MessagingServiceClient的方法,它仍然是一个很好的方法。

或.NET 4.0方式(无Task支持):

async/await

这有点偏离您目前正在做的事情,因为来电者负责确保调用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; } UI线程,如果他们想要在其上执行回调。但是,这是Login的标准做法,而 可以保留对您asyncSynchronizationContext的引用{1}} 强制要在正确的线程上执行回调/继续,它会破坏你的实现和个人(这只是我的意见)我会说这有点代码味道。