【问题标题】:What's the best way to cancel an asynchronous WCF request?取消异步 WCF 请求的最佳方法是什么?
【发布时间】:2011-03-03 15:09:15
【问题描述】:

(假设有一个名为“MyFunction”的 WCF 方法)

目前,为了支持取消 WCF 请求,我正在使用 svcutil 生成的 BeginMyFunction/EndMyFunction 方法(并在将结果分派到主线程时处理 isCanceled 标志)。我想使用 MyFunctionAsync 方法(并改为挂钩到 MyFunctionAsyncCompleted 事件)进行异步调用,而不是 Begin/End。

如果使用 MyFunctionAsyncCompleted,处理取消 WCF 请求的最佳/受支持方法是什么,并且仍然确保在不再加载的页面上不会触发事件(即框架内的页面导航)。

谢谢!

编辑:

我决定要在每次调用的基础上创建我的 WcfClient 对象(而不是每个 WPF 页面或每个应用程序),所以这是我想出的:

public void StartValidation(){
    WcfClient wcf = new WcfClient();
    wcf.IsValidCompleted += new EventHandler<IsValidCompletedEventArgs>(wcf_IsValidCompleted);
    //pass the WcfClient object as the userState parameter so it can be closed later
    wcf.IsValidAsync(TextBox.Text, wcf);  
}

void wcf_IsValidCompleted(object sender, IsValidCompletedEventArgs e) {
    if(!m_IsCanceled){
        //Update the UI
        //m_IsCanceled is set to true when the page unload event is fired
    }
    //Close the connection
    if (e.UserState is WcfClient) {
        ((WcfClient)e.UserState).Close();
    }
}

我发现很难弄清楚完成我刚刚实施的建议的方法是什么。这是原样的,还是我需要担心的陷阱/边缘情况?正确取消 WCF 调用的黄金标准是什么?

【问题讨论】:

  • 这样,您并没有真正取消呼叫(即它仍在运行完成。)您只是在检查是否应该忽略结果,对吗?
  • 非常正确。这现在可以确保至少我的 UI 在页面卸载后不会更新。我正在研究我应该在卸载事件中进行哪些调用序列(即关闭、中止等)以绕过我当前使用的这种“标记”技术。
  • 在卸载时调用Abort 并不总是取消请求(事实上,请求几乎总是完成,Fiddler 证明了这一点)。我在服务器端添加了 5 秒的延迟,让我有时间中止呼叫。这不是我所期望的。
  • 好吧,我想实际上不可能“取消”已经发送到服务器的请求。你会怎么做?追赶它并告诉它回来? (抱歉,无法抗拒 ;-) 所以归根结底,“取消”Web 服务请求只发生在应用层。我假设调用 Abort() 只会忽略来自服务器的待处理响应,并且可能会释放一些本来会在以后释放的资源。

标签: wpf wcf asynchronous


【解决方案1】:

我知道使用 WCF 客户端和基于任务的异步模式执行此操作的最简单方法是使用取消令牌注册 Abort 操作

private async Task CallOrAbortMyServiceAsync(CancellationToken cancellation)
{
    var client = new SomeServiceClient();
    cancellation.Register(() => client.Abort());
    try
    {
         await client.CallMyServiceAsync();
    } catch (CommunicationObjectAbortedException) {
          // This will be called when you are cancelled, or some other fault.
    }
}

【讨论】:

  • +1 不错的实现!我只是建议处理Cancellation.Register() 的结果,否则注册可能会泄漏。
【解决方案2】:

除非您手动创建异步函数,否则无法取消异步请求。考虑到您正在自动生成 WCF 调用,这会使它变得更加繁琐。即便如此,就像你说的那样,通话不会取消,它仍然会正常运行。如果您仍想取消,您只需确保客户端/UI 忽略调用的结果。

【讨论】:

    【解决方案3】:

    .Net 4.5

    1. 每个 WCF 调用实现都会创建一个 CancellationTokenSource 并将其存储在所有 WCF 服务都可以访问的位置。 IE。在单服务器环境中可以在内存缓存中。
    2. CancellationTokenSource.Token 被传递给由 WCF 方法发起的所有方法调用,包括对数据库的调用和适用的网络调用。
    3. WCF 方法可以将唯一标识符作为参数,也可以返回与 CancellationTokenSource 关联的唯一标识符。
    4. 当客户端需要取消操作时,调用 WCF 方法并传入前一次调用的唯一标识符。
    5. 使用唯一标识符检索 CancellationTokenSource 并调用其 Cancel 方法。如果取消令牌得到正确处理,之前调用启动的操作将很快被取消,并且可以返回 OperationCanceledException 或 CancelFault 或其他一些错误,表明客户端调用已取消。
    6. 当客户端调用在第 4 步同时取消时,可以中止原始 WCF 调用。但是,如果客户端正确处理 OperationCanceledException 或 CancelFault,则不需要这样做。如果原始调用是从网页开始的,OperationCanceledException 甚至会冒泡到 ajax 调用。

    类似的东西也可以在 CancellationToken 尚不可用的旧 .Net 框架中实现。

    【讨论】:

    • 感谢您的回答。一些参考资料或代码示例会很棒。
    【解决方案4】:

    仅仅由于您正在谈论的请求的异步性质,取消请求在一般意义上并不实际。可以执行特定的解决方法。例如,向您的协议添加一个新的 Cancel 信号,该信号设置一些共享状态,您的主要长期运行任务会定期检查以验证它是否应该继续执行。

    无论哪种方式,再次由于请求的异步性质,我认为知道忽略已取消请求的结果仍然是客户的责任。无法保证客户端不会收到来自请求的响应,该请求只是因为客户端和服务器之间存在竞争而被取消。可以添加一个额外的层来监视某些内容是否已被取消并知道丢弃请求的响应,但这并不妨碍将结果传输到客户端。

    【讨论】:

      【解决方案5】:

      如果我对您的理解正确,您正在尝试正确中止对 WCF 服务的挂起调用。您想使用 MyFunctionCompleted 事件,因为它是在 UI 线程中处理的。

      您可能应该做的是在WcfClient 上调用Abort 方法(您需要保留对它的引用)。这将清理客户端的资源。服务器仍将完成请求,但客户端将不再等待。在MyFunctionCompleted 事件触发后不久。通过检查client.State,您将知道调用是成功、失败还是中止。

      这是一个小型测试应用,有一个发送按钮、一个中止按钮和一个用于显示结果的文本框:

      public partial class MainForm : Form
      {
          public MainForm()
          {
              InitializeComponent();
          }
      
          private SomeServiceClient m_client;
      
          private void buttonSend_Click(object sender, EventArgs e)
          {
              m_client = new SomeServiceClient();
              m_client.MyFunctionCompleted += new EventHandler<MyFunctionCompletedEventArgs>(client_MyFunctionCompleted);
              m_client.MyFunctionAsync(4000, m_client);
          }
      
          private void buttonAbout_Click(object sender, EventArgs e)
          {
              if( m_client != null ) 
                  m_client.Abort();
          }
      
          void client_MyFunctionCompleted(object sender, MyFunctionCompletedEventArgs e)
          {
      
              var client = e.UserState as SomeServiceClient;
              if (client != null)
              {
                  if (client.State == System.ServiceModel.CommunicationState.Opened)
                  {
                      textBox.Text += string.Format("Result: {0}\r\n", e.Result);
                      client.Close();
                      return;
                  }
                  else if (client.State == System.ServiceModel.CommunicationState.Faulted)
                  {
                      textBox.Text += string.Format("Error: {0}\r\n", e.Error.Message);
                  }
                  client.Abort();
              }
          }
      }
      

      没有异常处理和清理...我不知道是不是推荐的方式,但我认为调用abort是正确的方式。您需要以任何方式处理不同的错误情况。

      【讨论】:

        猜你喜欢
        • 2013-10-13
        • 1970-01-01
        • 2019-08-05
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多