Как создать асинхронный метод в С#?

Каждое сообщение в блоге, которое я прочитал, говорит вам, как использовать асинхронный метод на С#, но по какой-то странной причине никогда не объясняет, как создавать собственные асинхронные методы для потребления. Поэтому у меня есть этот код прямо сейчас, который использует мой метод:

private async void button1_Click(object sender, EventArgs e)
{
    var now = await CountToAsync(1000);
    label1.Text = now.ToString();
}

И я написал этот метод, который CountToAsync:

private Task<DateTime> CountToAsync(int num = 1000)
{
    return Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}

Это, использование Task.Factory, лучший способ написать асинхронный метод, или я должен написать это другим способом?

Ответ 1

Я не рекомендую StartNew, если вам не нужен этот уровень сложности.

Если ваш метод async зависит от других методов async, самый простой способ - использовать ключевое слово async:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  for (int i = 0; i < num; i++)
  {
    await Task.Delay(TimeSpan.FromSeconds(1));
  }

  return DateTime.Now;
}

Если ваш метод async выполняет работу с ЦП, вы должны использовать Task.Run:

private static async Task<DateTime> CountToAsync(int num = 10)
{
  await Task.Run(() => ...);
  return DateTime.Now;
}

Вы можете найти мое async/await введение.

Ответ 2

Если вы не хотели использовать async/await внутри своего метода, но все же "украшаете" его, чтобы иметь возможность использовать ключевое слово ожидания извне, TaskCompletionSource.cs:

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; 
}

Отсюда и здесь

Чтобы поддерживать такую ​​парадигму с задачами, нам нужен способ сохранить фасад задачи и возможность ссылаться на произвольную асинхронную операцию как задачу, но контролировать время жизни этой задачи в соответствии с правилами базовой инфраструктуры что обеспечивает асинхронность и делает это таким образом, который не стоит значительно. Это цель TaskCompletionSource.

Я видел, что также используется в источнике .NET, например. WebClient.cs:

    [HostProtection(ExternalThreading = true)]
    [ComVisible(false)]
    public Task<string> UploadStringTaskAsync(Uri address, string method, string data)
    {
        // Create the task to be returned
        var tcs = new TaskCompletionSource<string>(address);

        // Setup the callback event handler
        UploadStringCompletedEventHandler handler = null;
        handler = (sender, e) => HandleCompletion(tcs, e, (args) => args.Result, handler, (webClient, completion) => webClient.UploadStringCompleted -= completion);
        this.UploadStringCompleted += handler;

        // Start the async operation.
        try { this.UploadStringAsync(address, method, data, tcs); }
        catch
        {
            this.UploadStringCompleted -= handler;
            throw;
        }

        // Return the task that represents the async operation
        return tcs.Task;
    }

Наконец, я нашел полезным и следующее:

Мне все время задают этот вопрос. Подразумевается, что там должен быть какой-то поток, который блокирует вызов ввода-вывода внешним ресурсом. Итак, асинхронный код освобождает поток запросов, но только за счет другого потока в другом месте в системе, правильно? Нет, совсем нет. Чтобы понять, почему асинхронные запросы масштабируются, Ill трассирует (упрощенный) пример асинхронного вызова ввода-вывода. Допустим, что запрос нужно записать в файл. Поток запроса вызывает метод асинхронной записи. WriteAsync реализуется библиотекой базового класса (BCL) и использует порты завершения для асинхронного ввода-вывода. Таким образом, вызов WriteAsync передается ОС в виде асинхронной записи файла. Затем ОС связывается со стеком драйверов, передавая данные для записи в пакете запроса ввода-вывода (IRP). Здесь все становится интересным: если драйвер устройства не может обрабатывать IRP немедленно, он должен обрабатывать его асинхронно. Таким образом, драйвер сообщает диску о начале записи и возвращает "ожидающий" ответ на ОС. ОС передает этот "ожидающий" ответ на BCL, а BCL возвращает неполную задачу в код обработки запроса. Код запроса обрабатывает задачу, которая возвращает неполную задачу из этого метода и так далее. Наконец, код обработки запроса заканчивается возвратом неполной задачи ASP.NET, и поток запроса освобождается, чтобы вернуться в пул потоков.

Введение в Async/Await в ASP.NET

Если целью является улучшение масштабируемости (а не отзывчивость), все зависит от наличия внешнего ввода-вывода, который предоставляет возможность сделать это.

Ответ 3

Просто добавьте пару слов в свой код - "асинхронный" и "ждущий":

private async Task<DateTime> CountToAsync(int num = 1000)
{
    return await Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < num; i++)
        {
            Console.WriteLine("#{0}", i);
        }
    }).ContinueWith(x => DateTime.Now);
}