Задачи не являются потоками, для их запуска не требуется вызов .Start
.Все примеры и учебные пособия показывают использование Task.Run
или Task.StartNew
по определенной причине - задачи - это обещание , что функция будет выполнена в какой-то момент в будущем и даст результат.Они будут работать в потоках, извлеченных из ThreadPool, когда планировщик задач решит, что должен.Создание холодных задач и вызов .Start
не гарантирует, что они начнутся, просто делает код намного сложнее для чтения.
В простейшем случае опрос, например удаленная конечная точка HTTP, может быть таким простым, как:
public static async Task Main()
{
var client=new HttpClient(serverUrl);
while(true)
{
var response=await client.GetAsync(relativeServiceUrl);
if(!response.IsSuccessStatusCode)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
Нет необходимости запускать новую задачу, поскольку GetAsync
является асинхронным.WCF и ADO.NET также предоставляют асинхронные методы выполнения.
Если нет асинхронного метода для вызова или если нам нужно выполнить некоторую тяжелую работу перед асинхронным вызовом, мы можем использовать Task.Run, чтобы запустить метод впараллельны и ждут завершения:
public bool CheckThatService(string serviceUrl)
{
....
}
public static async Task Main()
{
var url="...";
//...
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
//That was an error, do something with it
}
await Task.Delay(1000);
}
}
Что если мы хотим протестировать несколько систем параллельно?Мы можем запустить несколько задач параллельно, дождаться их завершения и проверить их результаты:
public static async Task Main()
{
var urls=new[]{"...","..."};
//...
while(true)
{
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses)
{
///Check the value, due something
}
await Task.Delay(1000);
}
}
Task.WhenAll
возвращает массив с результатами в порядке создания задач.Это позволяет проверять индекс, чтобы найти исходный URL.Лучшей идеей было бы вернуть результат и URL вместе, например, с помощью кортежей:
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
return (true,url);
}
Код не сильно изменился бы:
var tasks=urls.Select(url=>Task.Run(()=>CheckThatService(url));
var responses=await Task.WhenAll(tasks);
foreach(var response in responses.Where(resp=>!resp.ok))
{
///Check the value, due something
}
Что если бы мы хотели сохранитьрезультаты всех звонков?Мы не можем использовать список или очередь, потому что они не являются потокобезопасными.Вместо этого мы можем использовать ConcurrentQueue :
ConcurrentQueue<string> _results=new ConcurrentQueue<string>();
public static (bool ok,string url) CheckThatService(string serviceUrl)
{
....
_results.Enqueue(someresult);
return (true,url);
}
Если мы хотим регулярно сообщать о прогрессе, мы можем использовать IProgress<T>
, как показано в Включение выполнения и отмены в асинхронных API .
Мы могли бы поместить весь код мониторинга в отдельный метод / класс, который принимает параметр IProgress< T>
с объектом прогресса, который может сообщать об успехе, сообщения об ошибках и URL-адрес, который их вызвал, например:
class MonitorDTO
{
public string Url{get;set;}
public bool Success{get;set;}
public string Message{get;set;}
public MonitorDTO(string ulr,bool success,string msg)
{
//...
}
}
class MyMonitor
{
string[] _urls=url;
public MyMonitor(string[] urls)
{
_urls=url;
}
public Task Run(IProgress<MonitorDTO> progress)
{
while(true)
{
var ok=Task.Run(()=>CheckThatService(url));
if(!ok)
{
_progress.Report(new MonitorDTO(ok,url,"some message");
}
await Task.Delay(1000);
}
}
}
Этот класс можно использовать следующим образом:
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
await monitor.Run(progress);
}
Включение выполнения и отмены в асинхронных API показывает, как использовать CancellationTokenSource для реализации другой важной частикласс мониторинга - отмена его.Метод мониторинга может периодически проверять состояние токена отмены и останавливать мониторинг при его поднятии:
public Task Run(IProgress<MonitorDTO> progress,CancellationToken ct)
{
while(!ct.IsCancellationRequested)
{
//...
}
}
public static async Task Maim()
{
var ulrs=new[]{....};
var monitor=new MyMonitor(urls);
var progress=new Progress<MonitorDTO>(pg=>{
Console.WriteLine($"{pg.Success} for {pg.Url}: {pg.Message}");
});
var cts = new CancellationTokenSource();
//Not awaiting yet!
var monitorTask=monitor.Run(progress,cts.Token);
//Keep running until the first keypress
Console.ReadKey();
//Cancel and wait for the monitoring class to gracefully stop
cts.Cancel();
await monitorTask;
В этом случае цикл завершится при поднятии CancellationToken.Не ожидая MyMonitor.Run()
, мы можем продолжать работать с основным потоком, пока не произойдет событие, при котором мониторинг сигналов должен прекратиться.