Обтекание библиотеки, использующей асинхронный шаблон на основе событий, для использования с Async/Await
Я использую шаблон async/await во всем коде. Однако есть один API, который использовал асинхронный шаблон, основанный на событиях. Я читал в MSDN и несколько ответов на Stackru, что способ сделать это - использовать TaskCompletionSource.
Мой код:
public static Task<string> Process(Stream data)
{
var client = new ServiceClient();
var tcs = new TaskCompletionSource<string>();
client.OnResult += (sender, e) =>
{
tcs.SetResult(e.Result);
};
client.OnError += (sender, e) =>
{
tcs.SetException(new Exception(e.ErrorMessage));
};
client.Send(data);
return tcs.Task;
}
И называется как:
string result = await Process(data);
Или для тестирования:
string result = Process(data).Result;
Метод всегда возвращается очень быстро, но ни одно из событий не запускается.
Если я добавлю tcs.Task.Await(); перед оператором return он работает, но это не обеспечивает асинхронное поведение, которое я хочу.
Я сравнил с различными образцами, которые я видел в Интернете, но не вижу никакой разницы.
2 ответа
Проблема заключается в том, что после вашего Process
метод заканчивается, ваш ServiceClient
Локальная переменная подходит для сбора мусора и может быть собрана до запуска события, следовательно, имеется условие гонки.
Чтобы избежать этого, я бы определил ProcessAsync
как метод расширения для типа:
public static class ServiceClientExtensions
{
public static Task<string> ProcessAsync(this ServiceClient client, Stream data)
{
var tcs = new TaskCompletionSource<string>();
EventHandler resultHandler = null;
resultHandler = (sender, e) =>
{
client.OnResult -= resultHandler;
tcs.SetResult(e.Result);
}
EventHandler errorHandler = null;
errorHandler = (sender, e) =>
{
client.OnError -= errorHandler;
tcs.SetException(new Exception(e.ErrorMessage));
};
client.OnResult += resultHandler;
client.OnError += errorHandler;
client.Send(data);
return tcs.Task;
}
}
И потреблять это так:
public async Task ProcessAsync()
{
var client = new ServiceClient();
string result = await client.ProcessAsync(stream);
}
Редактировать:@usr указывает, что обычно операции ввода-вывода должны сохранять ссылки на тех, кто их вызвал, но это не так, как мы видим здесь. Я согласен с ним, что это поведение немного своеобразно и, вероятно, должно сигнализировать о какой-то проблеме проектирования / реализации ServiceClient
объект. Я бы посоветовал, если это возможно, посмотреть на реализацию и посмотреть, есть ли что-то, что может привести к тому, что ссылка не останется в корне.
Я думаю, я должен ответить на это.
public static Task<string> Process(Stream data)
{
var handle = new AutoResetEvent(false);
var client = new ServiceClient();
var tcs = new TaskCompletionSource<string>();
client.OnResult += (sender, e) =>
{
tcs.SetResult(e.Result);
handle.Set();
};
client.OnError += (sender, e) =>
{
tcs.SetException(new Exception(e.ErrorMessage));
handle.Set();
};
client.Send(data);
handle.WaitOne(10000); // wait 10 secondds for results
return tcs.Task;
}