eConti - программирование в вопросах и ответах

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

Изменить См. заголовок «Проблема» в конце моего вопроса, чтобы решить этот вопрос.

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

Редактировать. Я не могу пометить свои методы вызова уровня uber как 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 (запрос) приходит как null в методе Result, также Status = Faulted

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

2-b) Другой вариант - проверить в функции 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;
}

Редактировать 2 — на основе редактирования VMAtm, проверьте параметр OnlyOnFaulted (ошибка).

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 Основываясь на ответе Evk.

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);

  • Вы просто ждете каждую задачу, а затем пытаетесь поймать ее. Node JS Promises должны были предотвратить ад обратных вызовов. Async/Await в C# предотвращает ад обратного вызова. Таким образом, нет необходимости портировать мышление Node Js на C#, поскольку он уже имеет дело с этим сценарием. 07.02.2017
  • Мне нравится цепочка, которая поставляется с реализацией на основе задач, я буду ее активно использовать, но просто не могу получить ее прямо сейчас, я не уверен, почему Task.Result становится нулевым в моей функции Result, какие-либо исправления? 07.02.2017
  • Я бы посоветовал вам больше следовать стилю С#, потому что, если у вас есть другие разработчики или вы просматриваете документацию для фреймворков, вы/они могут запутаться, потому что у вас есть своего рода сдвиг парадигмы. Кроме того, вы можете столкнуться с проблемами позже при изменении ваших реализаций, когда требования изменятся. 07.02.2017
  • хорошо, цепочка с использованием ContinueWith сбивает с толку разработчиков C #, если тогда что является нормой? 07.02.2017
  • @user2727195 user2727195 Ну, в обычном случае ваш код неверен, и ваша обработка ошибок также очень неверна. Хотя возможно сделать правильно с ContinueWith, это довольно утомительно, трудно сделать точно правильно, и если вы сделаете это правильно, важный бизнес-код скроется в большом количестве механического кода. Использование await не имеет ни одной из этих проблем. Эти проблемы также становятся все более и более выраженными по мере усложнения кода. 07.02.2017

Ответы:


1

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

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, вы все равно можете пометить его как асинхронный:

public async void DoSomethingAsync(MyRequest request)
{
     try {
         await Customer.GetCustomersAsync(request);
         Result(request);      
     }
     catch (Exception ex) {
         Fault(request);
     }
}
08.02.2017
  • интересный подход, теперь, если мы расширим это, скажем, я помещу еще 5 chained ContinueWith async operations с опцией OnlyOnRanToCompletion в строку № 2 вашего кода и нажму Fault (строка 2) и Result (строка 3) вниз, будет ли это работать как положено, т.е. задачи будет продолжать выполнение до конца в случае успеха, а в случае неудачи он сразу перейдет к ошибке, пропуская любые промежуточные операции. 08.02.2017
  • пожалуйста, просмотрите Редактировать 3 на основе вашего ответа, в обработчике ошибок я назначил результат от t.Exception, чтобы сообщить звонящему Uber или запрашивающим клиентам, что пошло не так. 08.02.2017
  • Выглядит нормально, но обеспечьте надлежащую обработку ошибок: если Job2Async или Job3Async выдаст исключение, оно останется незамеченным и может привести к сбою всего процесса. Если внутри Job2Async и Job3Async есть блок try-catch, то все должно быть в порядке. Конечно, async-await намного чище и менее подвержен ошибкам, но если вам абсолютно необходимо использовать ContinueWIth, этого должно быть достаточно. 08.02.2017
  • о нет, Job2Async и Job3Async должны генерировать исключения, а не обрабатывать их, это иерархия uber, которая перехватывает все исключения и хорошо сообщает клиенту, и я полагаюсь на обработчик ошибок, указанный в конце цепочки, чтобы быть обработчиком ошибок GetCustomersAsync, Job2Async и Job3Async. Это значит, что я должен отказаться от этого маршрута??? 08.02.2017
  • Если вы по какой-либо причине отказываетесь использовать async\await, вам придется вручную обрабатывать исключения из Job2Async и Job3Async (вы можете использовать для них то же самое при продолжении ошибок). 08.02.2017
  • Повторение/дублирование продолжения сбоя после каждого ошибочного задания AsyncJob. Когда вы сказали, что я могу пометить метод void как асинхронный, что на самом деле открыло горизонт, я пробую другой ваш пример с await. 08.02.2017
  • Кроме того, чтобы уменьшить количество продолжений, вы можете просто использовать одно продолжение без опций (без OnFault или OnRanToCompletion). В этом продолжении просто проверьте свойство Exception и Result задачи (или IsCompleted и IsFaulted) и решите, что делать, основываясь на этом. В вашем последнем примере это уменьшит количество продолжений с 4 до 1. 08.02.2017
  • Я доволен async await сейчас, поворотным моментом стало то, что вы сказали, что мой метод может оставить недействительным, я не могу изменить свой контракт с вызывающими абонентами uber, запрос, который я получаю, в основном является шаблоном наблюдателя, и в результате/ошибке я уведомить uber о результате или исключении. Я попробовал предложенный вами маршрут для проверки IsCompleted и isFaulted, но он внес шум в мой код. await и async чисты и даже лучше, чем ContinueWith, и я с удовольствием использую их, сохраняя при этом свой метод недействительным. Спасибо за вашу замечательную помощь. 08.02.2017
  • Пожалуйста. Без сомнения, async\await лучше, чем ContinueWith, так что придерживайтесь его, когда это возможно. 08.02.2017
  • @user2727195 user2727195 в целом async void используется только для обработчиков асинхронных событий, и его следует избегать, однако, если вы не можете изменить подпись, это вариант. Возможно, вам потребуется добавить ConfigureAwait(false) к вашим await заявлениям. 08.02.2017

  • 2

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

    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 вызовет ошибку < a href="https://msdn.microsoft.com/en-us/library/system.aggregateexception.aspx" rel="nofollow noreferrer">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);
    }
    
    07.02.2017
  • хорошо, что я могу сделать с сигнатурами методов Job1 и Job2, есть несоответствие, возможно ли, чтобы оба метода получали request вместо Task<Request>, я пробовал с лямбдой (prevTask) => Delegate.Job2(request), есть идеи 07.02.2017
  • @ user2727195 В каком случае? Для await использования или для продолжения? 07.02.2017
  • @user2727195 user2727195 Почему вы не хотите ввести await и запустить DoSomethingAsync внутри лямбды? 07.02.2017
  • Task.Run(() => caller.DoSomethingAsync(request) нужна асинхронная лямбда, верно? 07.02.2017
  • @user2727195 user2727195 нет, вы можете запустить его синхронно 07.02.2017
  • ваш вопрос, почему вы не хотите вводить, потому что мой контроллер не хочет связываться с вызывающей стороной, контроллер отправляет запросы вызывающей стороне (своего рода асинхронно, это объект-наблюдатель с данными) и ожидает внутри метода обратного вызова (шаблон наблюдателя) для результатов. 07.02.2017
  • Я не понимаю, как это может быть связано с использованием await или ContinueWith. Я обновлю ответ дополнительными мыслями, 07.02.2017
  • также не могли бы вы помочь с этим, stackoverflow. com/questions/42097864/, это срочно 07.02.2017
  • @ user2727195 Я добавил новую информацию. Надеюсь, это поможет, и удачи в вашем проекте. 08.02.2017
  • несколько причин, по которым я придерживаюсь ContinueWith, во-первых, я следую шаблону проектирования под названием «FormalRequest», который трудно объяснить здесь, во-вторых, .ContinueWith выглядел похоже на промисы JS, которые действительно хорошо работали с последовательностью асинхронных заданий, небольшая разница здесь в c#, но работает (я вижу, что ошибка возникает раньше, и вы должны указать параметры продолжения для каждого), 3-й я не хочу иметь try/catch в caller, это работа ContinueWith для определения правильного метода result/fault для вызова, 08.02.2017
  • 4-й этот шаблон требует, чтобы вызывающая сторона не была асинхронной, контроллер uber не хочет присоединяться к вызывающей стороне и отправляет запрос на основе шаблона наблюдателя, 5-я косметика, мне понравились обещания, поэтому задачи, но после этого углубимся в асинхронность/ожидание, хотя я мне не нравится пытаться поймать на уровне вызывающей стороны, объекты делегата/помощника должны генерировать исключение, и за цепочку последовательности отвечает вызов результата/ошибки, но я попробую. 08.02.2017
  • теперь поймал, я редактирую свой вопрос под названием Edit2, мне нужно знать, какое исключение вызвало запуск для его просмотра для отчетов клиента, и эта информация доступна только с Task.Exception, это большой взлет глаз, любой совет. 08.02.2017
  • Я также сделал цепочку для вызова result и fault с request вместо Task для согласованности между всеми делегатами, помощниками и самой вызывающей программой. caller должен быть аккуратным, аккуратным и чистым, чтобы каждый метод вызывающего объекта представлял собой последовательность цепочек, в то время как вся грязная работа выполняется делегатами и помощниками, включая try/catch. 08.02.2017
  • Кстати, предложенное решение не работает, я пробовал с реальным кодом, см. Самый последний раздел моего вопроса под названием «Проблема». 08.02.2017
  • @user2727195 user2727195 Затем вам нужно добавить обработчик ошибок в каждую задачу внутри вашего кода. Если вы не используете метод try/catch — получите задачу из метода async и проверьте ее статус и исключения, если таковые имеются, в вызывающей программе, как я сделал в последнем фрагменте кода. 08.02.2017
  • Новые материалы

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

    Время расцвета закончилось
    Большую часть своей карьеры в индустрии программного обеспечения программисты работали с головой в песок. Успех в отрасли требует навыков презентации и обучения других. Ценность улучшенных..

    Будущее сельского хозяйства: новый уровень производительности с современными технологиями
    По мере роста населения мира растет и спрос на продукты питания. Фермеры сталкиваются с растущим давлением необходимости повышать урожайность и максимизировать производительность, манипулируя..

    Состояние совместной фильтрации в 2022 году, часть 1
    ResBeMF: Улучшение прогнозируемого охвата совместной фильтрации на основе классификации (arXiv) Автор: Анхель Гонсалес-Прието , Авраам Гутьеррес , Фернандо Ортега , Рауль Лара-Кабрера..

    Зачем изучать PYTHON в 2022 году !
    Python — востребованный, доступный язык программирования с активным, постоянно растущим сообществом пользователей. Для тех, кто хочет сменить профессию в мире технологий с помощью..

    Решение капч с помощью Puppeteer
    Это руководство предназначено для текстовых кодов, а не для reCAPTCHA Google (см. конец этого сообщения). Требования: Антикапча или любой другой сервис по разгадыванию капчи. Модуль..

    7 встроенных библиотек Python, которые необходимо знать
    7 встроенных библиотек Python, которые необходимо знать Стандартная библиотека Python значительно упрощает жизнь программистов, предоставляя широкий набор функций. Мы выбираем несколько..