Vlad
Vlad

Reputation: 3171

WCF client hangs on any operation after awaiting async operation in Console Application

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

Answers (1)

Vlad
Vlad

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

Related Questions