2

Для того, чтобы выполнить долговременную работу (пусть это будет поиск в этом контексте), я поместил логику загрузки в задачу TPL, поэтому общий метод Search() is называемый фоновой нитью. Поиск() операция может быть достаточно длинной, поэтому мне нужно отменить ее правильно, используя CancellationToken. Но операция Search() не вернулась, пока она не закончилась, поэтому я должен сделать некоторую логику, чтобы реализовать удобную и (!) Быстро отмена.Шаблон для долговременной работы с возможностью отмены

Использование WaitHandle's я могу реализовать что-то вроде этого:

private void StartSearch() // UI thread 
{ 
    CancellationTokenSource s = new CancellationTokenSource(); 
    Task.Factory.StartNew(() => StartSearchInternal(s.Token), s.Token) 
} 

private void StartSearchInternal(CancellationToken token) // Main Background Thread 
{ 
    ManualResetEvent eHandle = new ManualResetEvent(false); 
    Task.Factory.StartNew(() => Search(eHandle), TaskScheduler.Default); 
    WaitHandle.WaitAny(new [] { eHandle, token.WaitHandle }); 
    token.ThrowIfCancellationRequested(); 
} 

private IEnumerable<object> Search(ManualResetEvent e) // Another Background thread 
{ 
    try 
    { 
     // Real search call, i.e. to database, service, or AD, doesn't matter 
     return RealSearch(); 
    } 
    catch {} // Here, for simplicity of question, catch and eat all exceptions 
    finally 
    { 
     try 
     { 
      e.Set(); 
     } 
     catch {} 
    } 
} 

Это, мне кажется, что это не так элегантное решение, которое может быть сделано.

Вопрос: Есть ли какие-либо другие подходы для этой задачи?

+0

Помогло ли это? http://stackoverflow.com/questions/13513650/how-to-set-timeout-for-a-line-of-c-sharp-code/13513854 – Carsten

+0

@ Aschratt - Нет. Мне не нужны параметры TimeOut. Мне нужна быстрая отмена. – stukselbax

+1

Итак, вы не можете использовать 'ThrowIfCancellationRequested' внутри' StartSearchInternal', не так ли? Если это так, отметьте [«Как отменить отмененные асинхронные операции?»] (Http://blogs.msdn.com/b/pfxteam/archive/2012/10/05/how-do-i-cancel-non -cancelable Асинхр-operations.aspx). – Noseratio

ответ

1

Это мой комментарий, реорганизованный в ответ, содержащий код. Он содержит пару альтернатив для использования Task.Wait и асинхронного шаблона, выбор которого будет зависеть от того, вы вызываете этот метод из потока пользовательского интерфейса.

Есть несколько комментариев к O/P и другим ответам, которые содержат ценную информацию об асинхронном поведении. Пожалуйста, прочтите их, поскольку приведенный ниже код имеет много «возможностей для улучшения».

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Threading.Tasks; 

namespace SearchAgent 
{ 
    class CancellableSearchAgent 
    { 
     // Note: using 'object' is a bit icky - it would be better to define an interface or base class, 
     // or at least restrict the type of object in some way, such as by making CancellableSearchAgent 
     // a template CancellableSearchAgent<T> and replacing every copy of the text 'object' in this 
     // answer with the character 'T', then make sure that the RealSearch() method return a collection 
     // of objects of type T. 
     private Task<IEnumerable<object>> _searchTask; 
     private CancellationTokenSource _tokenSource; 

     // You can use this property to check how the search is going. 
     public TaskStatus SearchState 
     { 
      get { return _searchTask.Status; } 
     } 

     // When the search has run to completion, this will contain the result, 
     // otherwise it will be null. 
     public IEnumerable<object> SearchResult { get; private set; } 

     // Create a new CancellableSearchAgent for each search. The class encapsulates the 'workflow' 
     // preventing issues with null members, re-using completed tasks, etc, etc. 
     // You can add parameters, such as SQL statements as necessary. 
     public CancellableSearchAgent() 
     { 
      _tokenSource = new CancellationTokenSource(); 
      _searchTask = Task<IEnumerable<object>>.Factory.StartNew(() => RealSearch(), TaskScheduler.Default); 
     } 

     // This method can be called from the UI without blocking. 
     // Use this if the CancellableSearchAgent is part of your ViewModel (Presenter/Controller). 
     public async void AwaitResultAsync() 
     { 
      SearchResult = await _searchTask; 
     } 

     // This method can be called from the ViewModel (Presenter/Controller), but will block the UI thread 
     // if called directly from the View, making the UI unresponsive and unavailable for the user to 
     // cancel the search. 
     // Use this if CancellableSearchAgent is part of your Model. 
     public IEnumerable<object> AwaitResult() 
     { 
      if (null == SearchResult) 
      { 
       try 
       { 
        _searchTask.Wait(_tokenSource.Token); 
        SearchResult = _searchTask.Result; 
       } 
       catch (OperationCanceledException) { } 
       catch (AggregateException) 
       { 
        // You may want to handle other exceptions, thrown by the RealSearch() method here. 
        // You'll find them in the InnerException property. 
        throw; 
       } 
      } 
      return SearchResult; 
     } 

     // This method can be called to cancel an ongoing search. 
     public void CancelSearch() 
     { 
      _tokenSource.Cancel(); 
     } 
    } 
} 
+0

Я хочу сказать, что вы по-прежнему несете ответственность за исключение, попадающее в действие задачи TPL – stukselbax

+0

@stukselbax. Будет ли это в методе RealSearch, вызываемом действием Task или в методе AwaitResultAsync? Возможно, я должен был продублировать весь блок if (null == SearchResult) {...} в реализации AwaitResultAsync. –

+1

, который должен находиться в действии, переданном как параметр задачи TPL, так что это должно быть сделано в методе RealSearch. ** [AFAIK] (http://stackoverflow.com/questions/2707295/how-to-handle-all-unhandled-exceptions-when-using-task-parallel-library) **, в .net 4.0, если вы не сделали 't обрабатывает исключения в задаче - приложение потерпит неудачу, без полезной информации в журнале событий. В .net 4.5 эта политика была изменена, так что это не так. – stukselbax

2

Если у вас есть контроль над StartSearchInternal() и Search(eHandle), то вы должны быть в состоянии сделать совместную отмену с ThrowIfCancellationRequested внутри Search основного цикла.

Для получения более подробной информации, я настоятельно рекомендую прочитать этот документ: "Using Cancellation Support in .NET Framework 4".

На боковой ноте вы должны, вероятно, сохранить ссылку на задание, возвращаемое Task.Factory.StartNew(() => StartSearchInternal(s.Token), s.Token) где-то в вашем классе ViewModel. Вы, скорее всего, захотите увидеть его результат и любое исключение, которое оно может вызвать. Вы можете проверить Lucian Wischik's "Async re-entrancy, and the patterns to deal with it".

+0

Я забыл упомянуть, что я использую .net 4.0 – stukselbax

+0

@stukselbax, тогда у вас есть 'Task.ContinueWith' для обработки завершения задачи или вы все равно сможете использовать' async/await' с 'Microsoft.Bcl.Async' библиотека. Существуют [другие шаблоны] (http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx), в том числе 'IEnumerator' /' yield'. Во всяком случае, вы все равно должны хранить ссылку на задачу где-то. – Noseratio

Смежные вопросы