Когда следует использовать TaskCompletionSource?
AFAIK, все, что он знает, это то, что в какой-то момент его SetResult или SetException метод вызывается для завершения Task<T> разоблачили через Task собственность.
другими словами, он выступает в качестве производителя для Task<TResult> и до его завершения.
Я видел здесь пример :
если мне нужен способ выполнить функцию асинхронно и иметь задачу
представлять, что операция.
public static Task<T> RunAsync<T>(Func<T> function)
{
if (function == null) throw new ArgumentNullException(“function”);
var tcs = new TaskCompletionSource<T>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
T result = function();
tcs.SetResult(result);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
который можно было бы использовать *если бы у меня не было Task.Factory.StartNew -
Но Я do есть Task.Factory.StartNew.
вопрос:
может кто-нибудь объяснить на примере сценария, связанного напрямую до TaskCompletionSource
и не гипотетическая ситуацию, в которой у меня нет Task.Factory.StartNew?
9 ответов:
Я в основном использую его, когда доступен только API на основе событий (например Windows phone 8 сокеты):
public Task<Args> SomeApiWrapper() { TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>(); var obj = new SomeApi(); // will get raised, when the work is done obj.Done += (args) => { // this will notify the caller // of the SomeApiWrapper that // the task just completed tcs.SetResult(args); } // start the work obj.Do(); return tcs.Task; }Так что это особенно полезно при использовании вместе с c#5
asyncключевое слово.
по моему опыту,
TaskCompletionSourceотлично подходит для обертывания старых асинхронных шаблонов в современныйasync/awaitузор.самый полезный пример, который я могу придумать, это при работе с
Socket. Он имеет старые шаблоны APM и EAP, но неawaitable TaskметодыTcpListenerиTcpClientесть.у меня лично есть некоторые проблемы с
NetworkStreamкласс и предпочитают rawSocket. Будучи тем, что я также люблюasync/awaitшаблон, я сделал расширение классаSocketExtenderчто создает несколько методов расширения дляSocket.все эти методы использовать
TaskCompletionSource<T>чтобы обернуть асинхронные вызовы так:public static Task<Socket> AcceptAsync(this Socket socket) { if (socket == null) throw new ArgumentNullException("socket"); var tcs = new TaskCompletionSource<Socket>(); socket.BeginAccept(asyncResult => { try { var s = asyncResult.AsyncState as Socket; var client = s.EndAccept(asyncResult); tcs.SetResult(client); } catch (Exception ex) { tcs.SetException(ex); } }, socket); return tcs.Task; }я
socketнаBeginAcceptметоды, так что я получаю небольшое повышение производительности из компилятора, не поднимая локальный параметр.тогда красота всего этого:
var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610)); listener.Listen(10); var client = await listener.AcceptAsync();
для меня, классический сценарий использования
TaskCompletionSourceЭто когда возможно, что мой метод не будет обязательно должны сделать трудоемкую операцию. Что позволяет нам сделать, это выбрать конкретные случаи, когда мы хотим использовать новый поток.хороший пример для этого, когда вы используете кэш. Вы можете иметь
GetResourceAsyncметод, который ищет в кэше запрошенный ресурс и возвращает сразу (без использования нового потока, используяTaskCompletionSource), Если ресурс был найдено. Только если ресурс не был найден, мы хотели бы использовать новый поток и получить его с помощьюTask.Run().пример кода можно посмотреть здесь: как условно запустить код асинхронно с помощью задач
на этот блог, Levi Botelho описывает, как использовать
TaskCompletionSourceчтобы написать асинхронную оболочку для процесса, чтобы вы могли запустить его и дождаться его завершения.public static Task RunProcessAsync(string processPath) { var tcs = new TaskCompletionSource<object>(); var process = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo(processPath) { RedirectStandardError = true, UseShellExecute = false } }; process.Exited += (sender, args) => { if (process.ExitCode != 0) { var errorMessage = process.StandardError.ReadToEnd(); tcs.SetException(new InvalidOperationException("The process did not exit correctly. " + "The corresponding error message was: " + errorMessage)); } else { tcs.SetResult(null); } process.Dispose(); }; process.Start(); return tcs.Task; }и его использование
await RunProcessAsync("myexecutable.exe");
TaskCompletionSource используется для создания задание объекты, которые не выполнить код. В Реальных Сценариях TaskCompletionSource идеально подходит для операций ввода-вывода. Таким образом, вы получаете все преимущества задач (например, возвращаемые значения, продолжения и т. д.), не блокируя поток на время операции. Если ваша "функция" является операцией привязки ввода-вывода, не рекомендуется блокировать поток с помощью нового задание. Вместо использования TaskCompletionSource вы можете создать подчиненную задачу, чтобы просто указать, когда ваша операция ввода-вывода заканчивается или неисправна.
есть реальный пример с достойным объяснением в этом сообщение из блога "параллельное программирование с .NET". Вы действительно должны прочитать его, но вот резюме в любом случае.
сообщение в блоге показывает две реализации для:
"заводской метод для создания "отложенных" задач, которые не будут на самом деле планируется до тех пор, пока не произойдет некоторое время ожидания, предоставленное пользователем."
первая реализация на
Task<>и имеет два основных недостатка. Второй пост реализации продолжает смягчать их с помощьюTaskCompletionSource<>.вот эта вторая реализация:
public static Task StartNewDelayed(int millisecondsDelay, Action action) { // Validate arguments if (millisecondsDelay < 0) throw new ArgumentOutOfRangeException("millisecondsDelay"); if (action == null) throw new ArgumentNullException("action"); // Create a trigger used to start the task var tcs = new TaskCompletionSource<object>(); // Start a timer that will trigger it var timer = new Timer( _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite); // Create and return a task that will be scheduled when the trigger fires. return tcs.Task.ContinueWith(_ => { timer.Dispose(); action(); }); }
Я реальный сценарий мира, где я использовал
TaskCompletionSourceпри реализации очереди загрузки. В моем случае, если пользователь запускает 100 загрузок, я не хочу запускать их все сразу, и поэтому вместо возврата стратифицированной задачи я возвращаю задачу, прикрепленную кTaskCompletionSource. После завершения загрузки поток, который работает очередь завершает задачу.ключевая концепция здесь заключается в том, что я отделяю, когда клиент запрашивает задачу, которая должна быть запущена, когда она фактически получает начатый. В этом случае, потому что я не хочу, чтобы клиент имел дело с управлением ресурсами.
обратите внимание, что вы можете использовать async / await в .net 4, Если вы используете компилятор C# 5 (VS 2012+) см. здесь для более подробной информации.
Это может быть чрезмерно упрощая вещи, но источник TaskCompletion позволяет ждать на событие. Начиная с ТКС.SetResult устанавливается только после того, как событие происходит, вызывающий может ожидать на задаче.
смотрите это видео для получения дополнительной информации:
Comments