在等待控制台应用程序中的异步操作后,WCF客户端挂起任何操作

时间:2016-05-07 10:02:03

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

客户端示例代码:

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;
}

更新:它是一个控制台应用程序,因此没有同步上下文。如果我将await替换为.Wait()则可行。 ConfigureAwait(false)不会改变任何内容。

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

1 个答案:

答案 0 :(得分:2)

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

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

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