Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WinForms, .Net Framework Новый топик    Ответить
 Вызов асинхронного метода из синхронного  [new]
WinterGraveyard
Member

Откуда:
Сообщений: 77
Добрый день.
Такая ситуация:
1. Есть асинхронный метод, который, помимо await-вызовов, взаимодействует с UI (WPF, если это существенно).
2. Этот асинхронный метод, в зависимости от внешних обстоятельств, может выбрасывать ошибки, и такие ситуации нужно обрабатывать в т.ч. на уровне UI.
3. Модифицировать этот асинхронный метод (чтобы встроить туда обработку ошибок) нельзя (мопед не мой).
3. Этот асинхронный метод нужно вызвать из синхронного. При этом не нужно дожидаться окончания отработки этого метода, вполне устроит просто запустить его - но вот как быть с обработкой ошибок?
Попробовал вот так (схематично):
public ObservableCollection<string> Progress { get; } = new ObservableCollection<string>();
....
async Task AsyncTest()
{
  Console.WriteLine("AsyncTest: start");
  await Task.Delay(TimeSpan.FromSeconds(1));
  Progress.Add("one");
  await Task.Delay(TimeSpan.FromSeconds(1));
  Progress.Add("two");
  await Task.Delay(TimeSpan.FromSeconds(1));
  Progress.Add("three");
  Console.WriteLine("AsyncTest: before exception");
  new XmlDocument().LoadXml("");
  Progress.Add("four");
  Console.WriteLine("AsyncTest: end");
}

(Progress привязан в качестве ItemsSource к листбоксу - просто для имитации нескольких последовательных взаимодействий с UI).
Пытаюсь запустить:
void Test()
{
  Task.Factory
    .StartNew(
      async ()=>await AsyncTest(),
      CancellationToken.None,
      TaskCreationOptions.None,
      TaskScheduler.FromCurrentSynchronizationContext()
    )
    .ContinueWith(PrepareError, TaskContinuationOptions.OnlyOnFaulted);
}

void PrepareError(Task<Task> task)
{
  Console.WriteLine("Error occured");
}

Task.Factory.StartNew использовался для запуска потому, что он позволяет задать контекст синхронизации (а в Task.Run я такой возможности не нашёл). Требуемый метод запускается вполне нормально, первые 3 строки добавляются в листбокс, но PrepareError не вызывается - как выяснилось, по причине указания TaskContinuationOptions.OnlyOnFaulted. Если параметр с TaskContinuationOptions вообще убрать, то по консольному выводу видно, что PrepareError вызывается ещё до окончания отработки AsyncTest (и до возникновения ошибки). Как быть в данном случае?
19 апр 21, 15:03    [22311028]     Ответить | Цитировать Сообщить модератору
 Re: Вызов асинхронного метода из синхронного  [new]
Shocker.Pro
Member

Откуда: ->|<- :адуктО
Сообщений: 22341
WinterGraveyard
При этом не нужно дожидаться окончания отработки этого метода
ну так запусти его в отдельном потоке, при этом пусть поток ждет Result или Wait от этого метода (тогда он словит и исключение), но ты сам этот запущенный поток не жди, а только обрабатывай коллбэки.
19 апр 21, 15:24    [22311047]     Ответить | Цитировать Сообщить модератору
 Re: Вызов асинхронного метода из синхронного  [new]
fkthat
Member [заблокирован]

Откуда:
Сообщений: 4873
А что мешает сделать свой метод асинхронным как "async void":

async void Test()
{
    try 
    {
        await AsyncTest();
    }
    catch(Exception e)
    {
        Console.Write(e);
        // тут можно рисовать в гуй
    }
}
19 апр 21, 17:06    [22311109]     Ответить | Цитировать Сообщить модератору
 Re: Вызов асинхронного метода из синхронного  [new]
WinterGraveyard
Member

Откуда:
Сообщений: 77
fkthat,

Спасибо, и действительно - что-то я так завяз в разбирательстве с ContinueWith, что такой простой вариант упустил.
Однако хотелось бы всё же разобраться - просто для лучшего понимания - почему в моём вышеприведённом случае этот continuation task выполняется без ожидания выполнения асинхронного делегата? Та же внутри имеется await - получается, что он ничего не ждёт, а просто создаёт Task, запускает его, и возвращает выполнение? Несмотря на await?
20 апр 21, 06:54    [22311302]     Ответить | Цитировать Сообщить модератору
 Re: Вызов асинхронного метода из синхронного  [new]
Сон Веры Павловны
Member

Откуда:
Сообщений: 6174
WinterGraveyard
Та же внутри имеется await - получается, что он ничего не ждёт, а просто создаёт Task, запускает его, и возвращает выполнение? Несмотря на await?

Да, именно так. Task.Run с асинхронным делегатом и await ждёт, Task.Start - нет: https://stackoverflow.com/a/31584851
Using an async delegate inside Task.Run means that you actually run a Task<Task>. This is hidden from you by the fact that Task.Run is async aware and unwraps the inner task for you, something that Task.Factory.StartNew didn't do

и этот факт виден по требуемой сигнатуре Action для ContinueWith - в случае для Task.Run это Action<Task> (если не передавать состояние) - то есть действительно Task.Run unwraps the inner task for you.
А для Task.Start это Action<Task<Task>>, и никакого unwrap нет.
В данном случае, поскольку Task.Run использовать не получается, то можно сделать так:
void Test()
{
  var t = Task.Factory
    .StartNew(
      async () => await AsyncTest(),
      CancellationToken.None,
      TaskCreationOptions.None,
      TaskScheduler.FromCurrentSynchronizationContext()
    );
  t.Wait();
  t.Result.ContinueWith(
    PrepareError,
    null,
    CancellationToken.None,
    TaskContinuationOptions.OnlyOnFaulted,
    TaskScheduler.FromCurrentSynchronizationContext()
  );
}

void PrepareError(Task task, object o)
{
  Exception e = task.Exception;
  Console.WriteLine("Shit happens: {0}", e);
  if (e is AggregateException ae)
    e = ae.InnerException;
  Progress.Add($"{e.GetType()}: {e.Message}");
}

Я не думаю, что очень ожидание выполнения микротаска на старт другого таска будет критичным для чего-либо.
Но лучше, конечно, предложенный выше способ (с заворачиванием вызова метода в свой async-метод с обработкой ошибок). Он как минимум нагляднее.
20 апр 21, 11:32    [22311392]     Ответить | Цитировать Сообщить модератору
Все форумы / WinForms, .Net Framework Ответить