Подтвердить что ты не робот

Как правильно распоряжаться с помощью async и ждать

Я пытаюсь сделать замену кода от Thread до Task. Сон/задержка представляет собой только длинную работу.

static void Main(string[] args)
{
    ThreadDoWork();
    TaskDoWork();
}
public static void ThreadDoWork()
{
    using (var dispose = new ThreadDispose())
    {
        dispose.RunAsync();
    }
}
public static async void TaskDoWork()
{
    using (var dispose = new TaskDispose())
    {
        await dispose.RunAsync();
    }
}
public class ThreadDispose : IDisposable
{
    public void RunAsync ()
    {
        ThreadPool.QueueUserWorkItem(state =>
        {
            Thread.Sleep(3000);
        });
    }
    void IDisposable.Dispose()
    {
        File.AppendAllText("D:\\test.txt", "thread disposing");
    }
}
public class TaskDispose : IDisposable
{
    public async Task RunAsync()
    {
        await Task.Delay(3000);
    }
    void IDisposable.Dispose()
    {
        File.AppendAllText("D:\\test.txt", "task disposing");
    }
}

Результат через 3 секунды в test.txt равен

утилизация потоков

Что мне нужно изменить в порядке TaskDispose::Dispose всегда выполняется так же, как ThreadDispose?

4b9b3361

Ответ 1

Позволяет изолировать каждую часть кода:

public static void ThreadDoWork()
{
    using (var dispose = new ThreadDispose())
    { 
        dispose.RunAsync();
    }
}

public void RunAsync()
{
    ThreadPool.QueueUserWorkItem(state =>
    {
        Thread.Sleep(3000);
    });
}

Что вы делаете в этом первом фрагменте кода, это работа в очереди на поток threadpool. Поскольку вы выполняете этот код внутри области using и запускаете асинхронно в другом потоке, он предоставляет немедленно. Вот почему вы видите сообщение dispose внутри вашего текстового файла.

public static async void TaskDoWork()
{
   using (var dispose = new TaskDispose())
   {
       await dispose.RunAsync();
   }
}

public class TaskDispose : IDisposable
{
   public async Task RunAsync()
   {
       await Task.Delay(3000);
   }
}

Когда вы await внутри вашего метода, то, что вы на самом деле говорите, есть нечто вроде следующих: "Выполните этот код. Поскольку он асинхронен по своей природе, я верну управление обратно вызывающему методу, пожалуйста, позвоните мне, как только вы завершить асинхронную операцию".

Ваш код попадает в ключе await и возвращает управление вашему методу Main. Внутри Main ваш метод async - это последний фрагмент кода для выполнения, поэтому заканчивается ваше приложение и не дает возможности для выполнения вашего метода Dispose.

Если вы хотите его утилизировать, вам придется изменить тип возврата с void на Task и явно Wait:

public static async Task TaskDoWork()
{
    using (var dispose = new TaskDispose())
    {
       await dispose.RunAsync();
    }
}

И теперь:

static void Main(string[] args)
{
    ThreadDoWork();
    TaskDoWork().Wait();
}

Боковое примечание:

Существует несколько рекомендаций, которые следует соблюдать:

  • async void предназначен для совместимости с обработчиками событий, редко бывает за пределами этой области, где он должен использоваться. Вместо этого используйте async Task.

  • Методы, выполняющие асинхронную работу с использованием TAP (Task Asynchronous Pattern), должны заканчиваться postfix Async. TaskDoWork должен быть TaskDoWorkAsync

  • Использование Wait в Task может вызвать взаимоблокировки. В этом конкретном случае это происходит не потому, что консольное приложение не имеет SynchronizationContext и использует потоковые пулы. Рекомендованный подход заключается в том, чтобы "асинхронно полностью" и использовать await

В aync-wait tag wiki есть замечательные материалы для чтения. Обязательно проверьте его.

Ответ 2

Оба примера имеют проблемы. Во-первых, в поточном случае метод Dispose вызывает до окончания потока. В случае задачи метод Dispose будет вызываться, когда задача завершена.

Но файл имеет только текст, написанный потоком, потому что он вызван синхронно после вызова метода RunAsync. И случай Task ничего не пишет, потому что приложение заканчивается до завершения задачи.

Тест, чтобы ждать завершения задачи с помощью Thread.Sleep(5000), например, в конце метода Main, и задача должна записываться в выходной файл.