【问题标题】:Is there a reason to make every WCF call Async?是否有理由让每个 WCF 调用异步?
【发布时间】:2011-01-21 17:16:17
【问题描述】:

是否有理由让每个 WCF 服务调用异步?

我正在和我的搭档讨论这个问题。他想让每个 WPF 服务调用异步以避免锁定 UI(它是桌面 WPF 应用程序)。我反对这个想法。我觉得在大多数情况下不需要 Async 调用,当需要时,RequestingClass 和 DataManager 都应该专门编码来处理 Async 调用。

我的论点是为所有内容设置回调的代码要多得多,而且非常令人困惑。我也认为这可能会导致性能下降,尽管我还没有验证这一点。他的论点是,有时您会获取大量数据并且它会锁定 UI,并且像这样设置 WCF 调用并没有太多工作(他也没有发现以下代码令人困惑)。

我们之前都从未使用过 WCF 服务器,所以我想我会给他一个怀疑的好处,并在这里征求一些其他意见。

例如:

我的方式:

public override User GetById(int id)
{
    return new User(service.GetUserById(id));
}

它会锁定 UI、UserDataManager 和 WCF 服务通道,直到 WCF 服务器返回 User DataTransferObject,但它易于理解且编码快速。它将用于大多数 WCF 服务调用,除非它实际上预计在获取数据时会有延迟,在这种情况下,DataManager 将被设置为处理异步调用。

他的方式:

public override void GetById(int id, Action<UserGroup> callback = null)
{
    // This is a queue of all callbacks waiting for a GetById request
    if (AddToSelectbyIdQueue(id, callback))
        return;

    // Setup Async Call
    var wrapper = new AsyncPatternWrapper<UserDTO>(
        (cb, asyncState) => server.BeginGetUserById(id, cb, asyncState),
        Global.Instance.Server.EndGetUserById);

    // Hookup Callback
    wrapper.ObserveOnDispatcher().Subscribe(GetByIdCompleted);

    // Run Async Call
    wrapper.Invoke();
}

private void GetByIdCompleted(UserDTO dto)
{
    User user = new User(dto);

    // This goes through the queue of callbacks waiting 
    // for this method to complete and executes them
    RunSelectIdCallbacks(user.UserId, user);
}

基类回调队列:

/// <summary>
/// Adds an item to the select queue, or a current fetch if there is one
/// </summary>
/// <param name="id">unique object identifier</param>
/// <param name="callback">callback to run</param>
/// <returns>False if it needs to be fetched, True if it is already being
/// fetched</returns>
protected virtual bool AddToSelectbyIdQueue(int id, Action<T> callback)
{
    // If the id already exists we have a fetch function already going
    if (_selectIdCallbacks.ContainsKey(id))
    {
        if(callback != null)
            _selectIdCallbacks[id].Add(callback);
        return true;
    }

    if (callback != null)
    {
        List<Action<T>> callbacks = new List<Action<T>> {callback};
        _selectIdCallbacks.Add(id, callbacks);
    }

    return false;
}

/// <summary>
/// Executes callbacks meant for that object Id and removes them from the queue
/// </summary>
/// <param name="id">unique identifier</param>
/// <param name="data">Data for the callbacks</param>
protected virtual void RunSelectIdCallbacks(int id, T data)
{
    if (_selectIdCallbacks.ContainsKey(id))
    {
        foreach (Action<T> callback in _selectIdCallbacks[id])
            callback(data);

        _selectIdCallbacks.Remove(id);
    }
}

它不会锁定 UI、DataManager 或 WCF 服务通道,但是其中包含许多额外的编码。

无论如何,AsyncPatternWrapper 都在我们的应用程序中。它允许我们进行异步 WCF 调用并订阅回调事件

编辑 我们确实有一个包装器,我们可以从 UI 线程中使用它来包装任何 DataManager 调用。它在 BackgroundWorker 上执行 Synchronous 方法,并针对结果执行回调。

大部分额外代码是为了防止锁定 DataManager 和 WCF 服务通道。

【问题讨论】:

  • 考虑评估新 C#“async/await”功能的 CTP 版本。它旨在使编写此类代码更加容易。我们很乐意在异步论坛上获得您的反馈。 msdn.microsoft.com/en-us/vstudio/async.aspx
  • @Eric:谢谢,我现在正在研究它,到目前为止我很喜欢它!
  • @Eric:你能告诉我关于将它与 WCF 服务一起使用的任何好的教程/演练吗?

标签: c# wcf asynchronous


【解决方案1】:

你的搭档是正确的;你不应该阻塞 UI 线程。

作为异步调用的替代方法,您还可以使用 BackgroundWorker 或 ThreadPool 在后台线程中进行同步调用。

【讨论】:

  • 我们实际上确实有一个包装器可以做到这一点......它在 BackgroundWorker 线程中执行 DataManager 调用以停止锁定 UI,但是它是从 UI 线程而不是 DataManager 调用的(原因是WPF 无法更改从 BackgroundWorker 的 UI 线程上创建的对象)。我担心所有额外的编码只是停止锁定 DataManager 和 WCF 通道。我会更新我的问题。
  • 你还会推荐第二种方式吗?如果它是更好的做事方式,我没有问题,但如果不需要,我不想让事情过于复杂
  • @Rachel:您仍然应该为 Web 服务调用使用单独的线程,只需在调用返回时使用 Dispatcher.Invoke() 更新 UI - 大量信息来自 Google。
【解决方案2】:

有几点值得注意:

  1. 任何 WCF 调用都可能会阻塞,直到超时,您应该考虑到这一点。当在连接丢失的情况下阻止 UI 时,它会导致糟糕的用户体验。调用也可能比平时慢,例如,如果服务器陷入困境。

  2. 即使您不为异步目的包装调用,您也可能已经需要对代理的调用进行包装。这是因为对代理的任何调用都可能使通道处于故障状态,如果发生这种情况,您必须在通道上调用 Abort(),否则可能会泄漏资源。有关其他信息,请参阅 this postthis other post。 WCF 代理不能像普通类一样使用,如果您想在生产场景中使用它们,则必然涉及额外的包装。这在很大程度上是不可避免的,因为它是由远程通信要求引入的额外边界情况和不可预测行为的结果。

【讨论】:

  • 我删除了很多代码,所以我只能显示相关代码。 DataManager 中的所有服务调用都包含在捕获异常/故障并确保通道保持打开状态的东西中。大多数 DataManager 调用都包装在一个在 BackgroundWorker 上执行它们并停止 UI 阻塞的东西中。不过建议很好:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-19
相关资源
最近更新 更多