Когда следует использовать TaskCompletionSource<T>?
AFAIK, все, что он знает, это то, что в какой-то момент его SetResult
или же SetException
метод вызывается для завершения Task<T>
выставлено через его Task
имущество.
Другими словами, он выступает в качестве производителя для Task<TResult>
и его завершение.
Я видел здесь пример:
Если мне нужен способ выполнить Func асинхронно и иметь задачу для представления этой операции.
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
- Но у меня есть Task.Factory.StartNew
,
Вопрос:
Может кто-нибудь объяснить, например, сценарий, связанный непосредственно с TaskCompletionSource
а не в гипотетическую ситуацию, в которой у меня нет Task.Factory.StartNew
?
12 ответов
Я в основном использую его, когда доступен только 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
класс и предпочитаю сырье Socket
, То, что я тоже люблю 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()
,
Пример кода можно посмотреть здесь: Как условно выполнить код асинхронно с помощью задач
В этом сообщении блога Леви Ботельо описывает, как использовать 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 используется для создания объектов Task, которые не выполняют код. В реальных сценариях TaskCompletionSource идеально подходит для операций ввода-вывода. Таким образом, вы получаете все преимущества задач (например, возвращаемые значения, продолжения и т. Д.), Не блокируя поток на время выполнения операции. Если ваша "функция" является операцией, связанной с вводом-выводом, не рекомендуется блокировать поток, используя новую задачу. Вместо этого с помощью TaskCompletionSource вы можете создать подчиненную задачу, чтобы просто указать, когда ваша связанная операция ввода / вывода завершается или происходит сбой.
Похоже, никто не упомянул, но я думаю, что юнит-тесты тоже можно считать реальной жизнью.
я нахожу TaskCompletionSource
быть полезным при макете зависимости с помощью асинхронного метода.
В реальной тестируемой программе:
public interface IEntityFacade
{
Task<Entity> GetByIdAsync(string id);
}
В модульных тестах:
// set up mock dependency (here with NSubstitute)
TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();
IEntityFacade entityFacade = Substitute.For<IEntityFacade>();
entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);
// later on, in the "Act" phase
private void When_Task_Completes_Successfully()
{
queryTaskDriver.SetResult(someExpectedEntity);
// ...
}
private void When_Task_Gives_Error()
{
queryTaskDriver.SetException(someExpectedException);
// ...
}
В конце концов, такое использование TaskCompletionSource кажется еще одним случаем "объекта Task, который не выполняет код".
В этом посте приведен реальный пример с достойным объяснением из блога "Параллельное программирование с.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();
});
}
Это может быть слишком упрощением, но источник TaskCompletion позволяет ожидать события. Поскольку tcs. SetResult устанавливается только при возникновении события, вызывающая сторона может ожидать выполнения задачи.
Посмотрите это видео, чтобы узнать больше:
Я сценарий реального мира, где я использовал TaskCompletionSource
это при реализации очереди загрузки. В моем случае, если пользователь запускает 100 загрузок, я не хочу запускать их все сразу, и вместо того, чтобы возвращать объявленную задачу, я возвращаю задачу, прикрепленную к TaskCompletionSource
, После завершения загрузки поток, работающий в очереди, завершает задачу.
Ключевой концепцией здесь является то, что я отделяюсь, когда клиент запрашивает запуск задачи с момента ее фактического запуска. В этом случае, потому что я не хочу, чтобы клиенту приходилось иметь дело с управлением ресурсами.
обратите внимание, что вы можете использовать async/await в.net 4, если вы используете компилятор C# 5 (VS 2012+), смотрите здесь для получения дополнительной информации.
Я использовал TaskCompletionSource
выполнить задание, пока оно не будет отменено. В этом случае это подписчик ServiceBus, который я обычно хочу запускать до тех пор, пока приложение работает.
public async Task RunUntilCancellation(
CancellationToken cancellationToken,
Func<Task> onCancel)
{
var doneReceiving = new TaskCompletionSource<bool>();
cancellationToken.Register(
async () =>
{
await onCancel();
doneReceiving.SetResult(true); // Signal to quit message listener
});
await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}
TaskCompletionSource
это к задачам как WaitHandle
находится в теме. Итак, мы можем использоватьTaskCompletionSource
для выполнения точной сигнализации.
Примером является мой ответ на этот вопрос: задержка ContentDialog после нажатия кнопки ОК
Blazor WebAssemblyHost также использует это для предотвращения остановки виртуальной машины .NET.
await new TaskCompletionSource().Task;