2016-05-07 4 views
2

Client Пример кода:WCF клиент висит на любой операции после того, как ожидание асинхронной операции в консоли 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; 
} 

Update: это консольное приложение, так что нет контекста синхронизации. Если я заменю await с .Wait(), он будет работать. ConfigureAwait(false) ничего не меняет.

Я обнаружил, что продолжение так или иначе вызвано прямо из Task.SetResult. Почему он не вызывается через ThreadPool?

+0

В приложении Console метод 'Main' не может быть' async', поэтому в конечном итоге вам придется вызывать '.GetAwaiter(). GetResult()' внутри этого 'Main' метода, чтобы ждать всех асинхронных методов завершить. Конечно, внутри этих методов async вы можете использовать ключевое слово 'await' при вызове webservices. –

+0

@DarinDimitrov Да, я уже делаю так, иначе он не будет компилироваться. – Vlad

ответ

2

Когда нет SynchronizationContext, как с помощью ConsoleApplication TPL пытается оптимизировать вещи, вызвав продолжения непосредственно из Task.TrySetResult. Поскольку мой код вызывает WCF из продолжения, он вызывает тупик внутри внешнего кода WCF.

Решение должно состоять в том, чтобы поместить await Task.Yield(); после каждого await s.WcfMethod();, что вызывает последующее продолжение вызова с ThreadPool.

Лично я считаю, что это ошибка WCF: он должен позвонить Task.SetResult внутри Task.Run() или пройти до TaskCompletionSource.

Смежные вопросы