在控制台应用程序中等待异步操作后,WCF 客户端挂起任何操作
WCF client hangs on any operation after awaiting async operation in 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
实际上是第二次调用并达到其 return.
它不会影响其他客户端:他们可以连接并从头开始重复同样的事情。
行为:
[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
?
调用
当没有像 ConsoleApplication 这样的 SynchronizationContext
时,TPL 会尝试通过直接从 Task.TrySetResult
调用延续来优化事情。由于我的代码从延续中调用 WCF,因此它会导致外部 WCF 代码内部出现死锁。
解决方案是在每个 await s.WcfMethod();
之后放置 await Task.Yield();
,这会导致从 ThreadPool
.
调用下一个延续
我个人认为这是 WCF 的一个错误:它应该在 Task.Run()
中调用 Task.SetResult
或将 TaskCreationOptions.RunContinuationsAsynchronously 传递给 TaskCompletionSource
设置。
客户端示例代码:
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
实际上是第二次调用并达到其 return.
它不会影响其他客户端:他们可以连接并从头开始重复同样的事情。
行为:
[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
?
当没有像 ConsoleApplication 这样的 SynchronizationContext
时,TPL 会尝试通过直接从 Task.TrySetResult
调用延续来优化事情。由于我的代码从延续中调用 WCF,因此它会导致外部 WCF 代码内部出现死锁。
解决方案是在每个 await s.WcfMethod();
之后放置 await Task.Yield();
,这会导致从 ThreadPool
.
我个人认为这是 WCF 的一个错误:它应该在 Task.Run()
中调用 Task.SetResult
或将 TaskCreationOptions.RunContinuationsAsynchronously 传递给 TaskCompletionSource
设置。