【问题标题】:WCF client hangs on any operation after awaiting async operation in Console ApplicationWCF 客户端在控制台应用程序中等待异步操作后挂起任何操作
【发布时间】:2016-05-07 10:02:03
【问题描述】:

客户端示例代码:

var f = new DuplexChannelFactory<IService>(new Callback(), "NetTcpBinding_Name");
f.Credentials.ClientCertificate.Certificate = new X509Certificate2(certificateFile, "", X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
f.Open();
IService s = f.CreateChannel();
Console.WriteLine("Lock status: " + s.IsUserLocked(id));
Task task = s.LockUserAsync(id);
Console.Write("Sent, awaiting ");
Console.WriteLine(task);
await task;
Console.WriteLine("Done");
Console.WriteLine("Lock status: " + s.IsUserLocked(id)); // never returns, timeout

在等待异步方法LockUserAsync 之后,任何同步方法都会永远挂起。调试器显示IsUserLocked实际上被第二次调用并返回。

它不会影响其他客户端:它们可以连接并从头开始重复相同的操作。

行为:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, 
  ConcurrencyMode = ConcurrencyMode.Multiple)]

合同:

[ServiceContract(CallbackContract = typeof(IServiceCallback))]
public interface IService
{
    [OperationContract]
    Task LockUserAsync(int userId);
    [OperationContract]
    int IsUserLocked(int id);
}

public interface IServiceCallback
{
    [OperationContract]
    void TestCallback(); // not used
}

这两种方法都只是占位符:

public async Task LockUserAsync(int id)
{
    return;
}

public int IsUserLocked(int id)
{
    return 0;
}

更新:它是一个控制台应用程序,因此没有同步上下文。如果我用.Wait() 替换await,它就可以工作。 ConfigureAwait(false) 不会改变任何东西。

我发现继续以某种方式直接从Task.SetResult 调用。为什么不通过ThreadPool调用?

【问题讨论】:

  • 在控制台应用程序中,Main 方法不能是 async,因此最终您将不得不在此 Main 方法中调用 .GetAwaiter().GetResult() 以等待所有异步方法完成.当然,在那些async 方法中,您可以在调用网络服务时使用await 关键字。
  • @DarinDimitrov 是的,我已经这样做了,否则它将无法编译。

标签: c# wcf asynchronous async-await console-application


【解决方案1】:

当没有 SynchronizationContext 这样的 ConsoleApplication TPL 尝试通过直接从 Task.TrySetResult 调用延续来优化事物。由于我的代码从 continuation 调用 WCF,因此会导致外部 WCF 代码内部出现死锁。

解决方案是将await Task.Yield(); 放在每个await s.WcfMethod(); 之后,这会导致从ThreadPool 调用下一个继续。

我个人认为这是WCF的一个错误:它应该在Task.Run()内部调用Task.SetResult或将TaskCreationOptions.RunContinuationsAsynchronously传递给TaskCompletionSource设置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-05
    • 1970-01-01
    • 2023-03-08
    • 2019-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多