Reputation: 3171
Client example code:
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
After awaiting on an async method LockUserAsync
any sync methods hang forever. The debugger shows that IsUserLocked
is actually called second time and reached its return.
It doesn't affect other clients: they can connect and repeat the same thing from start.
Behavior:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
ConcurrencyMode = ConcurrencyMode.Multiple)]
Contract:
[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
}
Both methods are just placeholders:
public async Task LockUserAsync(int id)
{
return;
}
public int IsUserLocked(int id)
{
return 0;
}
Update: it's a console application so no synchronization context. If I replace await
with .Wait()
it works. ConfigureAwait(false)
doesn't change anything.
I've found that the continuation is somehow directly called from Task.SetResult
. Why it's not invoked through ThreadPool
?
Upvotes: 3
Views: 838
Reputation: 3171
When there is no SynchronizationContext
like with ConsoleApplication TPL tries to optimize things by invoking continuations directly from Task.TrySetResult
. Since my code calls WCF back from continuation it causes a deadlock inside external WCF code.
The solution was to put await Task.Yield();
after each await s.WcfMethod();
which causes next continuation to be invoked from ThreadPool
.
Personally I think that it's a bug of WCF: it should call Task.SetResult
inside Task.Run()
or pass TaskCreationOptions.RunContinuationsAsynchronously to TaskCompletionSource
settings.
Upvotes: 4