Цепные задачи в 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);
Проблема здесь (и в оригинальном примере тоже) заключается в следующем:
- Ты продолжай
GetCustomersAsync
с продолжением "только при неисправности". - Затем продолжите это продолжение, а не
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);
}