Цепные задачи в csharp с обработчиком успеха и ошибок

Редактировать Смотрите заголовок "Проблема" в конце моего вопроса, чтобы разобрать этот вопрос.

Исходя из nodejs, где мы могли бы связать обещания, в C# я вижу асинхронные задачи, почти сопоставимые. Вот моя попытка.

Редактировать - я не могу пометить методы вызова вызывающего пользователя как async как библиотека на основе DLL вызывает его

Вызывающий объект

public void DoSomething(MyRequest request) 
{
    Delegate.Job1(request)
        .ContinueWith(Delegate.Job2)
        .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
        .ContinueWith(Result);
}

public void Result(Task<MyRequest> task)
{
    MyRequest request = task.Result;
    Console.Writeline(request.result1 + " " + request.result2);
}

public void Fault(Task<MyRequest> task)
{
   MyRequest request = task.Result;
   Console.Writeline(request.result);
}

Делегировать объект

public async Task<MyRequest> Job1(MyRequest request) 
{
    var data = await remoteService.Service1Async();
    request.result1 = data;
    return request;
}

public async Task<MyRequest> Job2(Task<MyRequest> task)
{
    var data = await remoteService.Service2Async();
    request.result2 = data;
    return request;
}

Проблема:

1) Редактировать (исправлено, связанная DLL к моему проекту отсутствовала, это связанная DLL) Task.Result (запрос) приходит как ноль в Result метод, также Status = Faulted

2) Также корректна ли обработка ошибок? Я ожидаю, что Fault будет вызываться только тогда, когда в методах Delegate возникает исключение, в противном случае его следует пропустить.

2-б) Другим вариантом является проверка в Result функция (удалить Fault функция) если Task.status = RanTocompletion и ветвь там для успеха или ошибки

Редактировать после ответа

У меня есть ошибка, что если я не могу сделать свой контроллер асинхронным.

контроллер

public void ICannotBeAsync()
{
    try
    {
        var r = await caller.DoSomething(request); // though I can use ContinueWith here, ???
    }
    catch(Exception e)
    {
        //exception handling
    }
}

гость

public async Task DoSomethingAsync(MyRequest request)
{
     request.result1 = await delegateInstance.Job1(request);
     request.result2 = await delegateInstance.Job2(request);
     Console.Writeline(request.result1 + " " + request.result2);
     return result;
}

Edit 2 - на основе VMAtm Edit, пожалуйста, просмотрите опцию OnlyOnFaulted (Fault).

Delegate.Job1(request)
    .ContinueWith(_ => Delegate.Job2(request), TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(() => {request.result = Task.Exception; Fault(request);}, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion);

Проблема -

Дал тест, фактический код ниже, ни один из Result или же Fault вызывается, хотя метод GetCustomersAsync вернулся успешно. Мое понимание все останавливается на Fault потому что он помечен для запуска Fault только выполнение останавливается там и Result обработчик не вызывается.

Customer.GetCustomersAsync(request)
    .ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

Редактировать 3 Опираясь на ответ Эвка.

Task<Request> task = Customer.GetCustomersAsync(request);
task.ContinueWith(_ => Job2Async(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(_ => Job3Async(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(_ => Result(request), TaskContinuationOptions.OnlyOnRanToCompletion);
task.ContinueWith(t => { request.Result = t.Exception; Fault(request); }, TaskContinuationOptions.OnlyOnFaulted);

2 ответа

Решение

Здесь много чего сказано, поэтому я отвечаю только на последний раздел "Проблема":

Customer.GetCustomersAsync(request)
    .ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

Проблема здесь (и в оригинальном примере тоже) заключается в следующем:

  1. Ты продолжай GetCustomersAsync с продолжением "только при неисправности".
  2. Затем продолжите это продолжение, а не GetCustomersAsyncсо следующим продолжением, которое может быть запущено только после завершения.

В результате оба продолжения могут выполняться только тогда, когда GetCustomersAsync выходит из строя. Чинить:

var request = Customer.GetCustomersAsync(request);
request.ContinueWith(_ => { Debug.WriteLine("not executing"); Fault(request); }, TaskContinuationOptions.OnlyOnFaulted);
request.ContinueWith(_ => { Debug.WriteLine("not executing either"); Result(request); }, TaskContinuationOptions.OnlyOnRanToCompletion);

Обратите внимание, что даже если вы не можете изменить сигнатуру какого-либо метода и он обязательно должен вернуть void, вы все равно можете пометить его как async:

public async void DoSomethingAsync(MyRequest request)
{
     try {
         await Customer.GetCustomersAsync(request);
         Result(request);      
     }
     catch (Exception ex) {
         Fault(request);
     }
}

Есть несколько проблем с этим кодом:

Delegate.Job1(request)
    .ContinueWith(Delegate.Job2)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result);

Прежде всего, вы продолжаете казнь с Delegate.Job2 даже если Delegate.Job1 не удалось. Так что вам нужно OnlyOnRanToCompletion значение здесь. Похож на Result продолжение, вы продолжаете во всех случаях, так что задача с ошибкой все еще проходит через цепочку и, как вы уже видите, находится в Faulted состояние с null в следствии.

Итак, ваш код, если вы не можете использовать на этом уровне await, может быть так (также, как сказал @Evk, вам пришлось добавить обработку исключений ко всему вашему коду, что ужасно):

Delegate.Job1(request)
    .ContinueWith(Delegate.Job2, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted);

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

public async Task DoSomethingAsync(MyRequest request)
{
    try
    {
         request.result1 = await delegateInstance.Job1(request);
         request.result2 = await delegateInstance.Job2(request);
         Console.Writeline(request.result1 + " " + request.result2);
         return result;
     }
     catch(Exception e)
     {

     }
}

public void ICannotBeAsync()
{
    var task = Task.Run(() => caller.DoSomethingAsync(request);
    // calling the .Result property will block current thread
    Console.WriteLine(task.Result);
}

Обработка исключений может выполняться на любом уровне, так что вам решать, где это сделать. Если что-то пойдет не так во время исполнения, Result собственность поднимет AggregateException как обертка к внутренним исключениям произошла во время звонка. Также вы можете использовать Wait метод для задачи, завернутый в try/catch и проверьте состояние задачи после этого, и работайте с ней, как вам нужно IsFaulted, IsCompleted, IsCanceled булевы свойства).

Кроме того, настоятельно рекомендуется использовать логику отмены для задач, ориентированных на задачи, чтобы можно было отменить ненужную работу. Вы можете начать с этой статьи MSDN.

Обновление на основе ваших других вопросов:

Если вы все еще хотите использовать ContinueWith вместо await и хочу изменить подписи Job1, Job2 методы, вы должны изменить свой код следующим образом:

Delegate.Job1(request)
    .ContinueWith(_ => Delegate.Job2(request), TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Result, TaskContinuationOptions.OnlyOnRanToCompletion)
    .ContinueWith(Fault, TaskContinuationOptions.OnlyOnFaulted);

Причина этого заключается в том, что ContinueWith метод принимает Func<Task, Task> потому что вам нужно, как правило, проверять задачу на предмет ее статуса и / или результата.

Что касается вопроса о том, чтобы не блокировать абонента, вы можете попробовать TaskCompletionSource<TResult> класс, как то так:

public void ICannotBeAsync()
{
    var source = new TaskCompletionSource<TResult>();
    var task = Task.Run(() => caller.DoSomethingAsync(request, source);
    while (!source.IsCompleted && !source.IsFaulted)
    {
        // yeild the execution to other threads for now, while the result isn't available
        Thread.Yeild();
    }
}

public async Task DoSomethingAsync(MyRequest request, TaskCompletionSource<TResult> source)
{
     request.result1 = await delegateInstance.Job1(request);
     request.result2 = await delegateInstance.Job2(request);
     Console.Writeline(request.result1 + " " + request.result2);
     source.SetResult(result);
}
Другие вопросы по тегам