2

Как обращаться с регистром, где пользователь может нажать кнопку, которая вызывает длительную работу async, несколько раз.Правильно отмените операцию async и снова запустите его

Моя идея была сначала проверена, работает ли операция async, отмените ее и снова запустите.

До сих пор я пытался создать такую ​​функциональность, используя CancellationTokenSource, но он не работает должным образом. Несколько раз выполняется две асинхронные операции, поэтому «старые» асинхронные отписки еще не отменены, когда я запускаю новый, и это смешивает обработку resul.

Любые предложения или примеры, как обращаться с подобным корпусом?

public async void Draw() 
{ 
    bool result = false; 

    if (this.cts == null) 
    { 
     this.cts = new CancellationTokenSource(); 

     try 
     { 
      result = await this.DrawContent(this.TimePeriod, this.cts.Token); 
     } 
     catch (Exception ex) 
     {} 
     finally 
     { 
      this.cts = null; 
     } 
    } 

    else 
    { 
     this.cts.Cancel(); 
     this.cts = new CancellationTokenSource(); 

     try 
     { 
      result = await this.DrawContent(this.TimePeriod, this.cts.Token); 
     } 
     catch (Exception ex) 
     {} 
     finally 
     { 
      this.cts = null; 
     } 
    } 

} 

EDIT: В конце концов, я думаю, что это не плохо, что есть две операции асинхронных выполняющиеся в короткий промежуток времени (когда новый обжигали, но старый еще не отменен).

Настоящая проблема заключается в том, как я показываю прогресс для enduser. Как и при завершении старой операции async, он скрывает индикатор прогресса от enduser, но вновь запущенная операция async все еще работает.

EDIT2: Inside DrawContent (...) Я использую ThrowIfCancellationRequested, поэтому отмена выполняемой задачи, похоже, работает нормально.

О выставке прогресса. Когда вызывается Draw(), я устанавливаю индикатор загрузки, и когда этот метод заканчивается, я скрываю индикатор загрузки. Итак, теперь, когда предыдущая операция async отменяется после того, как я запускаю новый, мой индикатор загрузки скрыт. Как мне отслеживать, если еще один асинхронный метод все еще работает, когда заканчивается «старый».

+0

* как * не работает. Не говорите, что * это не работает. Конечно, глотание всех исключений - вообще плохая идея. Не делай этого. Вы никогда не узнаете, что случилось, когда вы это сделаете. – Servy

+0

Обновленный вопрос. – devha

+1

Ну, конечно, старый все еще может бежать. Никогда не ожидайте, пока предыдущая операция не закончится, прежде чем продолжить. Если есть время между запросом на отмену и завершением задачи, на это время будет выполняться два задания. – Servy

ответ

3

Я хотел бы воспользоваться уточнением some related code. В вашем случае его можно использовать, как показано ниже.

Обратите внимание, что если предыдущий экземпляр ожидающей операции потерпел неудачу (выброшен ничего, кроме OperationCanceledException), вы все равно увидите сообщение об ошибке. Такое поведение можно легко изменить.

Это только скрывает интерфейс прогресса, если по окончании операции, если она по-прежнему самый последний экземпляр задачи: if (thisTask == _draw.PendingTask) _progressWindow.Hide();

Этот код не поточно-как (_draw.RunAsync нельзя назвать одновременно) и предназначен для вызова из потока пользовательского интерфейса.

Window _progressWindow = new Window(); 

AsyncOp _draw = new AsyncOp(); 

async void Button_Click(object s, EventArgs args) 
{ 
    try 
    { 
     Task thisTask = null; 
     thisTask = _draw.RunAsync(async (token) => 
     { 
      var progress = new Progress<int>(
       (i) => { /* update the progress inside progressWindow */ }); 

      // show and reset the progress 
      _progressWindow.Show(); 
      try 
      { 
       // do the long-running task 
       await this.DrawContent(this.TimePeriod, progress, token); 
      } 
      finally 
      { 
       // if we're still the current task, 
       // hide the progress 
       if (thisTask == _draw.PendingTask) 
        _progressWindow.Hide(); 
      } 
     }, CancellationToken.None); 
     await thisTask; 
    } 
    catch (Exception ex) 
    { 
     while (ex is AggregateException) 
      ex = ex.InnerException; 
     if (!(ex is OperationCanceledException)) 
      MessageBox.Show(ex.Message); 
    } 
} 

class AsyncOp 
{ 
    Task _pendingTask = null; 
    CancellationTokenSource _pendingCts = null; 

    public Task PendingTask { get { return _pendingTask; } } 

    public void Cancel() 
    { 
     if (_pendingTask != null && !_pendingTask.IsCompleted) 
      _pendingCts.Cancel(); 
    } 

    public Task RunAsync(Func<CancellationToken, Task> routine, CancellationToken token) 
    { 
     var oldTask = _pendingTask; 
     var oldCts = _pendingCts; 

     var thisCts = CancellationTokenSource.CreateLinkedTokenSource(token); 

     Func<Task> startAsync = async() => 
     { 
      // await the old task 
      if (oldTask != null && !oldTask.IsCompleted) 
      { 
       oldCts.Cancel(); 
       try 
       { 
        await oldTask; 
       } 
       catch (Exception ex) 
       { 
        while (ex is AggregateException) 
         ex = ex.InnerException; 
        if (!(ex is OperationCanceledException)) 
         throw; 
       } 
      } 
      // run and await this task 
      await routine(thisCts.Token); 
     }; 

     _pendingCts = thisCts; 

     _pendingTask = Task.Factory.StartNew(
      startAsync, 
      _pendingCts.Token, 
      TaskCreationOptions.None, 
      TaskScheduler.FromCurrentSynchronizationContext()).Unwrap(); 

     return _pendingTask; 
    } 
} 
0

Вызов cts.Cancel() не будет автоматически останавливать задачу. Ваша задача должна активно проверять, была ли запрошена отмена. Вы можете сделать что-то вроде этого:

public async Task DoStuffForALongTime(CancellationToken ct) 
{ 
    while (someCondition) 
    { 
     if (ct.IsCancellationRequested) 
     { 
      return; 
     } 

     DoSomeStuff(); 
    } 
} 
+0

Как это сделать, чтобы убедиться, что предыдущий экземпляр задачи полностью отменен и не перекрывается с новым? – Noseratio

+0

Я использую ThrowIfCancellationRequested для отмены async-операции. – devha

0

Почему бы не следовать шаблону BackgroundWorker и не выйти из цикла в DrawContent?

private bool _cancelation_pennding=false; 
private delegate DrawContentHandler(TimePeriod period, Token token) 
private DrawContentHandler _dc_handler=null; 

.ctor(){ 
    this._dc_handler=new DrawContentHandler(this.DrawContent) 
} 
public void CancelAsync(){ 
    this._cancelation_pennding=true; 
} 
public void Draw(){ 
    this._dc_handler.BeginInvoke(this.TimePeriod, this.cts.Token) 
} 
private void DrawContent(TimePeriod period, Token token){ 
    loop(){ 
     if(this._cancelation_pennding) 
     { 
      break; 
     } 

     //DrawContent code here 
    } 
    this._cancelation_pennding=false; 
}