我有一个后台任务,我按以下方式运行。
这是对文本框文本更改事件的附加行为。
我想要的是,如果文本被更改然后再次更改,则在第二次更改时检查上一个任务是否仍在运行,如果是,请将其停止并继续使用最新任务。
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);
}
}
答案 0 :(得分:4)
我有点担心你提议在非UI线程上搜索“DataGrid中的对象属性” - 这可能非常有用,因为你不是设置来自后台的任何值线程,但有一点气味。
暂时忽略这一点,让我提出以下解决方案:
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;
}
}
答案 1 :(得分:1)
如果您传入取消令牌,从CancellationTokenSource传递到任务并通过递增变量来保持您更改文本的次数,您将能够通过调用来检查此值并取消任务token.Cancel(),并调用ThrowIfCancellationRequested,它会引发OperationCancelled异常。
必须通过TaskContinuationOptions.OnlyOnCanceled传递延续,以便仅在引发取消时执行,否则在其他任务完成时将引发。 (代码示例)