1

У меня есть длинная работа, которую я накладываю на фоновый поток, используя TPL. То, что я сейчас работаю, но меня смущает, где я должен обращаться со своим AggregateException во время запроса на отмену.Как правильно отменить задачу TPL с продолжением

В случае нажатия кнопки я начинаю процесс:

private void button1_Click(object sender, EventArgs e) 
{ 
    Utils.ShowWaitCursor(); 
    buttonCancel.Enabled = buttonCancel.Visible = true; 
    try 
    { 
     // Thread cancellation. 
     cancelSource = new CancellationTokenSource(); 
     token = cancelSource.Token; 

     // Get the database names. 
     string strDbA = textBox1.Text; 
     string strDbB = textBox2.Text; 

     // Start duplication on seperate thread. 
     asyncDupSqlProcs = 
      new Task<bool>(state => 
       UtilsDB.DuplicateSqlProcsFrom(token, mainForm.mainConnection, strDbA, strDbB), 
       "Duplicating SQL Proceedures"); 
     asyncDupSqlProcs.Start(); 

     //TaskScheduler uiThread = TaskScheduler.FromCurrentSynchronizationContext(); 
     asyncDupSqlProcs.ContinueWith(task => 
      { 
       switch (task.Status) 
       { 
        // Handle any exceptions to prevent UnobservedTaskException.    
        case TaskStatus.Faulted: 
         Utils.ShowDefaultCursor(); 
         break; 
        case TaskStatus.RanToCompletion: 
         if (asyncDupSqlProcs.Result) 
         { 
          Utils.ShowDefaultCursor(); 
          Utils.InfoMsg(String.Format(
           "SQL stored procedures and functions successfully copied from '{0}' to '{1}'.", 
           strDbA, strDbB)); 
         } 
         break; 
        case TaskStatus.Canceled: 
         Utils.ShowDefaultCursor(); 
         Utils.InfoMsg("Copy cancelled at users request."); 
         break; 
        default: 
         Utils.ShowDefaultCursor(); 
         break; 
       } 
      }, TaskScheduler.FromCurrentSynchronizationContext()); // Or uiThread. 

     return; 
    } 
    catch (Exception) 
    { 
     // Do stuff... 
    } 
} 

В методе DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true) меня

DuplicateSqlProcsFrom(CancellationToken _token, SqlConnection masterConn, string _strDatabaseA, string _strDatabaseB, bool _bCopyStoredProcs = true, bool _bCopyFuncs = true) 
{ 
    try 
    { 
     for (int i = 0; i < someSmallInt; i++) 
     { 
      for (int j = 0; j < someBigInt; j++) 
      { 
       // Some cool stuff... 
      } 

      if (_token.IsCancellationRequested) 
       _token.ThrowIfCancellationRequested(); 
     } 
    } 
    catch (AggregateException aggEx) 
    { 
     if (aggEx.InnerException is OperationCanceledException) 
      Utils.InfoMsg("Copy operation cancelled at users request."); 
     return false; 
    } 
    catch (OperationCanceledException) 
    { 
     Utils.InfoMsg("Copy operation cancelled at users request."); 
     return false; 
    } 
} 

В нажатие кнопки события (или с помощью delegate (buttonCancel.Click + = делегат {/ Отменить задание /} ) I cancel the Задача` следующим образом:

private void buttonCancel_Click(object sender, EventArgs e) 
{ 
    try 
    { 
     cancelSource.Cancel(); 
     asyncDupSqlProcs.Wait(); 
    } 
    catch (AggregateException aggEx) 
    { 
     if (aggEx.InnerException is OperationCanceledException) 
      Utils.InfoMsg("Copy cancelled at users request."); 
    } 
} 

Это ловит OperationCanceledException штрафа в методе DuplicateSqlProcsFrom и печатает мое сообщение, но в обратном вызове, представленной asyncDupSqlProcs.ContinueWith(task => { ... }); выше task.Status всегда RanToCompletion; его следует отменить!

Каков наилучший способ захвата и решения задачи Cancel() в этом случае. Я знаю, как это делается в простых случаях, показанных в this example from the CodeProject, и от examples on MSDN, но в этом случае я запутался при выполнении продолжения.

Как я могу захватить задачу отмены в этом случае и как обеспечить правильное управление task.Status?

ответ

3

Вы поймать OperationCanceledException в вашем DuplicateSqlProcsFrom метод, который предотвращает его Task из когда-либо видеть его и, соответственно, установив его статус Canceled. Поскольку обрабатывается исключение, DuplicateSqlProcsFrom заканчивается, не выбрасывая никаких исключений, и соответствующая задача заканчивается в состоянии RanToCompletion.

DuplicateSqlProcsFrom не следует ловить либо OperationCanceledException, либо AggregateException, если только он не ждет своих подзадач. Любые исключения, которые были выбраны (включая OperationCanceledException), должны быть оставлены неактивными для распространения в задаче продолжения. В заявлении вашего продюсера switch вы должны проверить task.Exception в футляре Faulted и обработать Canceled в соответствующем случае.

В вашем продолжении лямбда, task.Exception будет AggregateException, в котором есть некоторые удобные методы определения, в чем была основная причина ошибки, и обработки. Проверьте MSDN docs, в частности, на InnerExceptions (обратите внимание на «S»), GetBaseException, Flatten и Handle участников.


EDIT: на получение TaskStatus из Faulted вместо Canceled.

На линии, где вы построить свой asyncDupSqlProcs задачу, использовать Task конструктор, который принимает как свой DuplicateSqlProcsFrom делегат и CancellationToken. Это связывает ваш токен с задачей.

Когда вы вызываете ThrowIfCancellationRequested на токен в DuplicateSqlProcsFrom, вбрасываемый OperationCanceledException содержит ссылку на токен, который был отменен. Когда задача улавливает исключение, она сравнивает эту ссылку с ассоциированным с ней CancellationToken. Если они совпадают, то задача переходит к Canceled. Если они этого не делают, инфраструктура Task была написана, чтобы предположить, что это непредвиденная ошибка, а задача переходит на Faulted.

Task Cancellation in MSDN

+0

Спасибо за это. Это действительно помогло. Я дошел до стадии удаления обработок из метода 'DuplicateSqlProcsFrom', но все еще пытался захватить его в другом месте. – MoonKnight

+0

Я сделал то, что вы предлагаете, но в моем методе продолжения я получаю условие 'TaskStatus.Faulted', никогда не' TaskStatus.Cancelled'. Вы знаете, почему это может быть? – MoonKnight

+0

Я добавил объяснение и решение этого поведения для ответа. – shambulator

0

Sacha Barber имеет отличную серию статей о TPL. Попробуйте one, он описывает простую задачу с продолжением и отмены

+1

Хотя это теоретически может ответить на вопрос, [было бы предпочтительнее] (http://meta.stackexchange.com/q/8259), чтобы включить основные части ответа здесь, и обеспечить ссылка для справки. –

+0

Это ссылка, которую я предоставил. Хотя он охватывает отмену задач, он не находится в более сложном сценарии, который у меня есть здесь, поэтому мне бесполезно. Спасибо за ваше время. – MoonKnight