【问题标题】:WPF/WCF Async Service Call and SynchronizationContextWPF/WCF 异步服务调用和 SynchronizationContext
【发布时间】:2014-09-30 20:08:17
【问题描述】:

我觉得肯定有比我想出的更好的解决方案;问题来了:

WPF 表单将调用 WCF 方法,该方法返回一个布尔值。调用本身不应该在 UI 线程上,调用的结果需要显示在表单上,​​所以返回应该被编组回 UI 线程。

在本例中,我创建了一个“ServiceGateway”类,表单将向该类传递一个在完成登录操作时要执行的方法。网关应使用 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 端实现异步行为会更好吗?基本上,如果我可以更通用地为其他方法实现上述代码,或者是否有更好的方法,我很感兴趣。

【问题讨论】:

  • 您应该能够使用基于 Task 的异步 API 重新创建 WCF 客户端,这将大大清除这一点。

标签: c# wpf multithreading wcf


【解决方案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密集型工作推送到线程池,所以上面的例子确实有些滥用,但是如果你不想重写MessagingServiceClient实现的契约来使用异步@987654327基于@的方法,它仍然是一个不错的方法。

或.NET 4.0 方式(不支持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,如果他们希望在其上执行回调。然而,这是async 的标准做法,虽然您可以保留对 UI 的引用 SynchronizationContextTaskScheduler 作为 ChatServiceGateway 的一部分强制回调/继续在正确的线程上执行,它会破坏你的实现和个人(这只是我的意见)我会说这有点代码味道.

【讨论】:

  • 完美,正是我想知道的。我的方法似乎有些不对劲,绝对闻起来很有趣。谢谢!
  • 不客气。我想补充的最后一件事:请注意斯蒂芬克利里在他对这个问题的评论中所说的话。如果您可以使用包含基于 Task 的异步方法重新生成 WCF 代理,那么您的 Gateway 类可以简化为超精益抽象或完全消除。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-05-04
  • 2012-10-03
  • 1970-01-01
  • 2011-06-29
  • 1970-01-01
  • 2020-01-21
  • 2018-09-07
相关资源
最近更新 更多