Использование async в неасинхронном методе

Предположим, что мне нужен только один метод для запуска в async.

Итак, у меня есть метод async, как показано ниже:

public async Task Load(){
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
    var data2 = await task2;
    var data3 = await task3;
    ..process data..
}

И затем я пытаюсь вызвать этот метод async в другом методе как задачу, и хотел бы, чтобы он подождал, пока этот конкретный фрагмент кода async не будет выполнен. Проблема в том, что нет. Когда он достигает первого await в Load(), он просто не заканчивает загрузку. Отладчик пуст и не дает никакой другой ошибки.

Можно ли вызывать метод async из метода non async, как это? Есть причина, по которой мне не нужна эта конкретная задача async, но функция Load(), которую я делаю.

public void GetSomethingElse(){
    var task1 = Load().Wait();      
}

Как это возможно?


Я попытался даже изменить метод Load() на использование var data = task1.Wait() и т.д. вместо await, но все равно никакой разницы, независимо от того, каким способом я пытаюсь. Если кто-то может помочь, это будет оценено.

Ответы

Ответ 1

У вас наверняка есть тупик на ваших руках. Вы блокируете поток с помощью Wait() для задачи, которая нуждается в этом потоке, потому что там используется SynchronizationContext в ASP.Net(также в среде графического интерфейса).

Вы должны использовать ConfigureAwait(false), чтобы сообщить awaiter не фиксировать этот контекст. Этого достаточно, чтобы сделать это на первом await, так как в остальном не было бы SynchronizationContext для захвата:

public async Task Load()
{
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1.ConfigureAwait(false);
    var data2 = await task2;
    var data3 = await task3;
    //..process data.
}

Тем не менее, рекомендуется всегда использовать ConfigureAwait, если вы хотите захватить SynchronizationContext, поэтому лучшим стандартом является следующее:

public async Task Load()
{
    Task task1 = GetAsync(1);
    Task task2 = GetAsync(2);
    Task task3 = GetAsync(3);
    var data1 = await task1.ConfigureAwait(false);
    var data2 = await task2.ConfigureAwait(false);
    var data3 = await task3.ConfigureAwait(false);
    //..process data.
}

В вашем случае, когда вы хотите продолжить выполнение all завершенных задач, вы должны использовать Task.WhenAll вместо await для каждой задачи отдельно:

public async Task Load()
{
    await Task.WhenAll(GetAsync(1), GetAsync(2), GetAsync(3)).ConfigureAwait(false);
    // process data.
}

Примечание. Выполнение синхронизации по async обычно обескураживается, поскольку оно не имеет преимуществ (вы блокируете поток на протяжении всей операции) и может вызывать взаимоблокировки (например, этот).

Ответ 2

Я думаю, что у вас классический сценарий тупиковой ситуации. Подробнее см. этот пост. Когда у вас есть оператор await, текущий SynchronizationContext сохраняется перед вызовом await и восстанавливается впоследствии, а остальная часть метода отправляется на него. В приложении с графическим интерфейсом есть только один поток, связанный с этим контекстом, поэтому остальная часть метода будет выполняться в потоке GUI, но не может потому что Wait() - это блокирующий вызов, который блокирует поток GUI.

Попробуйте это вместо:

public async Task Load(){
    Task task1 = GetAsync(1).ConfigureAwait(false);
    Task task2 = GetAsync(2).ConfigureAwait(false);
    Task task3 = GetAsync(3).ConfigureAwait(false);

    var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
    var data2 = await task2;
    var data3 = await task3;
    ..process data..
}

Если в GetAsync есть какие-либо ожидания, возможно, вам также придется добавить .ConfigureAwait(false).

Ответ 3

Вы изменили бы свою функцию загрузки следующим образом:

public async Task Load(){
    await new TaskFactory().StartNew(() =>
    {
        Task task1 = GetAsync(1);
        Task task2 = GetAsync(2);
        Task task3 = GetAsync(3);
        var data1 = await task1; // <--Freezes here when called from GetSomethingElse()
        var data2 = await task2;
        var data3 = await task3;
        ..process data..
    });
}