2014-06-24 1 views
1

다음과 같은 방법으로 백그라운드 작업을 수행합니다.이전 작업이 아직 실행 중인지 확인하고 중지/취소하는 방법은 무엇입니까?

이것은 텍스트 상자 텍스트 변경 이벤트의 연결된 동작입니다.

텍스트가 변경된 후 다시 변경된 경우, 이전 작업이 아직 실행 중인지 두 번째 변경 사항 확인시, 이전 작업이 계속 실행 중인지 확인한 후 중지하고 최신 항목으로 계속 진행하십시오.

public class FindTextChangedBehavior : Behavior<TextBox> 
{ 
    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     AssociatedObject.TextChanged += OnTextChanged; 
    } 

    protected override void OnDetaching() 
    { 
     AssociatedObject.TextChanged -= OnTextChanged; 
     base.OnDetaching(); 
    } 

    private void OnTextChanged(object sender, TextChangedEventArgs args) 
    { 
     var textBox = (sender as TextBox); 
     if (textBox != null) 
     { 
      Task.Factory.StartNew(() => 
      { 
       //Do text search on object properties within a DataGrid 
       //and populate temporary ObservableCollection with items. 
       ClassPropTextSearch.init(itemType, columnBoundProperties); 

       if (itemsSource != null) 
       { 
        foreach (object o in itemsSource) 
        { 
         if (ClassPropTextSearch.Match(o, searchValue)) 
         { 
          tempItems.Add(o); 
         } 
        } 
       } 

       //Copy temporary collection to UI bound ObservableCollection 
       //on UI thread 
       Application.Current.Dispatcher.Invoke(new Action(() => MyClass.Instance.SearchMarkers = tempItems)); 
      }); 
     } 
    } 

[편집] 나는 이것을 아직 테스트하지 않았다.

CancellationTokenSource CancellationTokenSource = new CancellationTokenSource(); 

private void OnTextChanged(object sender, TextChangedEventArgs args) 
{ 
    var newCts = new CancellationTokenSource(); 
    var oldCts = Interlocked.Exchange(ref this.CancellationTokenSource, newCts); 

    if (oldCts != null) 
    { 
     oldCts.Cancel(); 
    } 

    var cancellationToken = newCts.Token; 

    var textBox = (sender as TextBox); 
    if (textBox != null) 
    { 
     ObservableCollection<Object> tempItems = new ObservableCollection<Object>(); 
     var ui = TaskScheduler.FromCurrentSynchronizationContext(); 

     var search = Task.Factory.StartNew(() => 
     { 
      ClassPropTextSearch.init(itemType, columnBoundProperties); 

      if (itemsSource != null) 
      { 
       foreach (object o in itemsSource) 
       { 
        cancellationToken.ThrowIfCancellationRequested(); 
        if (ClassPropTextSearch.Match(o, searchValue)) 
        { 
         tempItems.Add(o); 
        } 
       } 
      } 
     }, cancellationToken); 

     //Still to be considered. 
     //If it gets to here and it is still updating the UI then 
     //what to do, upon SearchMarkers being set below do I cancel 
     //or wait until it is done and continue to update again??? 
     var displaySearchResults = search.ContinueWith(resultTask => 
        MyClass.Instance.SearchMarkers = tempItems, 
        CancellationToken.None, 
        TaskContinuationOptions.OnlyOnRanToCompletion, 
        ui); 
    } 
} 
당신이 작업에 CancellationTokenSource에서 취소 토큰을 전달하고 변수를 증가시켜 텍스트를 변경 횟수의 수를 유지하는 경우

enter image description here

답변

3

을 (코드 예제 따라하기) 배경 스레드에서하지만 약간의 냄새가 있습니다. 지금 그 무시

, 나에게 다음과 같은 솔루션을 제안하자

private readonly SemaphoreSlim Mutex = new SemaphoreSlim(1, 1); 
private CancellationTokenSource CancellationTokenSource; 

private void OnTextChanged(object sender, TextChangedEventArgs args) 
{ 
    var newCts = new CancellationTokenSource(); 
    var oldCts = Interlocked.Exchange(ref this.CancellationTokenSource, newCts); 

    if (oldCts != null) 
    { 
     oldCts.Cancel(); 
    } 

    var cancellationToken = newCts.Token; 

    var textBox = (sender as TextBox); 
    if (textBox != null) 
    { 
     // Personally I would be capturing 
     // TaskScheduler.FromCurrentSynchronizationContext() 
     // here and then scheduling a continuation using that (UI) scheduler. 
     Task.Factory.StartNew(() => 
     { 
      // Ensure that only one thread can execute 
      // the try body at any given time. 
      this.Mutex.Wait(cancellationToken); 

      try 
      { 
       cancellationToken.ThrowIfCancellationRequested(); 

       RunSearch(cancellationToken); 

       cancellationToken.ThrowIfCancellationRequested(); 

       //Copy temporary collection to UI bound ObservableCollection 
       //on UI thread 
       Application.Current.Dispatcher.Invoke(new Action(() => MyClass.Instance.SearchMarkers = tempItems)); 
      } 
      finally 
      { 
       this.Mutex.Release(); 
      } 
     }, cancellationToken); 
    } 
} 

편집

지금 당신이 async - 인식 프레임 워크를 타겟팅 것을 알고 있기 때문에를, 위의 솔루션이 될 수 있습니다 모두 단순화되고 향상되었습니다.

"그리드 속성"이 수확되는 방법에 대한 수많은 가정을해야하고 실제 검색에서 디스패처 스레드로 실행되어야하는 프로세스를 분리하려고했습니다. 스레드 풀에서).

public class FindTextChangedBehavior : Behavior<TextBox> 
{ 
    protected override void OnAttached() 
    { 
     base.OnAttached(); 
     AssociatedObject.TextChanged += OnTextChanged; 
    } 

    protected override void OnDetaching() 
    { 
     AssociatedObject.TextChanged -= OnTextChanged; 
     base.OnDetaching(); 
    } 

    private CancellationTokenSource CancellationTokenSource; 

    // We're a UI handler, hence async void. 
    private async void OnTextChanged(object sender, TextChangedEventArgs args) 
    { 
     // Assume that this always runs on the UI thread: 
     // no thread safety when exchanging the CTS. 
     if (this.CancellationTokenSource != null) 
     { 
      this.CancellationTokenSource.Cancel(); 
     } 

     this.CancellationTokenSource = new CancellationTokenSource(); 

     var cancellationToken = this.CancellationTokenSource.Token; 

     var textBox = (sender as TextBox); 
     if (textBox != null) 
     { 
      try 
      { 
       // If your async work completes too quickly, 
       // the dispatcher will be flooded with UI 
       // update requests producing a laggy user 
       // experience. We'll get around that by 
       // introducing a slight delay (throttling) 
       // before going ahead and performing any work. 
       await Task.Delay(TimeSpan.FromMilliseconds(100), cancellationToken); 

       // Reduce TaskCanceledExceptions. 
       // This is async void, so we'll just 
       // exit the method instead of throwing. 

       // IMPORTANT: in order to guarantee that async 
       // requests are executed in correct order 
       // and respond to cancellation appropriately, 
       // you need to perform this check after every await. 
       // THIS is the reason we no longer need the Semaphore. 
       if (cancellationToken.IsCancellationRequested) return; 

       // Harvest the object properties within the DataGrid. 
       // We're still on the UI thread, so this is the 
       // right place to do so. 
       IEnumerable<GridProperty> interestingProperties = this 
        .GetInterestingProperties() 
        .ToArray(); // Redundant if GetInterestingProperties returns a 
           // list, array or similar materialised IEnumerable. 

       // This appears to be CPU-bound, so Task.Run is appropriate. 
       ObservableCollection<object> tempItems = await Task.Run(
        () => this.ResolveSearchMarkers(interestingProperties, cancellationToken) 
       ); 

       // Do not forget this. 
       if (cancellationToken.IsCancellationRequested) return; 

       // We've run to completion meaning that 
       // OnTextChanged has not been called again. 
       // Time to update the UI. 
       MyClass.Instance.SearchMarkers = tempItems; 
      } 
      catch (OperationCanceledException) 
      { 
       // Expected. 
       // Can still be thrown by Task.Delay for example. 
      } 
      catch (Exception ex) 
      { 
       // This is a really, really unexpected exception. 
       // Do what makes sense: log it, invalidate some 
       // state, tear things down if necessary. 
      } 
     } 
    } 

    private IEnumerable<GridProperty> GetInterestingProperties() 
    { 
     // Be sure to return a materialised IEnumerable, 
     // i.e. array, list, collection. 
     throw new NotImplementedException(); 
    } 

    private ObservableCollection<object> ResolveSearchMarkersAsync(
     IEnumerable<GridProperty> interestingProperties, CancellationToken cancellationToken) 
    { 
     var tempItems = new ObservableCollection<object>(); 

     //Do text search on object properties within a DataGrid 
     //and populate temporary ObservableCollection with items. 
     foreach (var o in interestingProperties) 
     { 
      cancellationToken.ThrowIfCancellationRequested(); 

      if (ClassPropTextSearch.Match(o, searchValue)) 
      { 
       tempItems.Add(o); 
      } 
     } 

     return tempItems; 
    } 
} 
+0

확실히 4.5까지 타겟팅. 당신이 제안한 것을 그냥 실행하십시오. – Hank

+0

그럴 경우 위의 코드를 상당히 개선 할 수 있습니다. Simpler/bettet 예외 처리, 조절 (비동기 임에도 불구하고 현재 구현에서 UI가 상당히 느려지는 것을 느낄 수 있습니다.) 등이 있습니다. 나는 자유 분이 생겼을 때 몇 가지 추가적인 명제로 나의 대답을 편집 할 것이다. –

+0

감사합니다! 또한 위의 예제에 추가되어 현재 작업의 내용을 보여줍니다. – Hank

1

, 당신은 확인할 수있을 것입니다 이 값은 token.Cancel()을 호출하여 작업을 취소하고 OperationCancelled 예외를 발생시키는 ThrowIfCancellationRequested를 호출합니다.

취소가 발생할 때만 실행하려면 계속하려면 TaskContinuationOptions.OnlyOnCanceled를 전달해야합니다. 그렇지 않으면 다른 작업이 완료 될 때 발생합니다. 당신이 모든 값을 설정하지 않는 것 같은이 아주 잘 작동 할 수 - 당신이 비 UI 스레드에서 "데이터 그리드 내에서 개체 속성"을 통해 트롤 어업 제안에 대해 조금 걱정

+0

것은 좀 MSDN 예제 (http://msdn.microsoft.com/en-us/library/dd997396%28v=vs.110%29.aspx)에서 보았다, 지금은 내가 '무엇을 알고 찾고있는, 그냥 조금 더 붙인 방법에 붙어있는 것, 당신은 함께 모범을 보일 수 있겠는가. – Hank

+0

난 그냥 뭔가를 집어 넣으려고하고있다. 실제로는 처음에는 생각하지 않았지만 각 버튼 클릭시 작업을 시작하고 싶지는 않지만 오히려 작업의 범위를 동작이나 양식으로 확장해야합니다. 행동의 평생/범위는 무엇입니까? – brumScouse

+0

DataGrid의 셀에서 텍스트 일치를 검색하고 있습니다. 기본적으로 ObservableCollection에 일치하는 항목 개체가 채워집니다. 120,000 개의 항목을 실행하는 데 몇 초가 걸립니다. – Hank

관련 문제