2

I have a WPF window that needs to react to SizeChanged events. However, it should only perform processing when there are no further SizeChanged events for a 500 ms period (similar to the behaviour offered by BindingBase.Delay).

private CancellationTokenSource lastCts;

private async void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (lastCts != null)
        lastCts.Cancel();
    lastCts = new CancellationTokenSource();

    try
    {
        await Task.Delay(500, lastCts.Token);
    }
    catch (OperationCanceledException)
    {
        return;
    }

    myTextBox.Text = string.Format("({0}, {1})", this.Width, this.Height);            
}

However, I have noticed that, when compiled as x64 under Debug mode, this code causes to UI to start lagging when being resized; there are perceptible delays in the window getting redrawn. I assume that this is due to the OperationCanceledException getting serialized, thrown, and caught on the UI thread. The code below eliminates the problem:

    Task.Delay(500, lastCts.Token).ContinueWith(
        _ =>
        {
            myTextBox.Text = string.Format("({0},{1})", this.Width, this.Height);
        },
        lastCts.Token,
        TaskContinuationOptions.NotOnCanceled,
        TaskScheduler.FromCurrentSynchronizationContext());

My question is: Is there a clean way of configuring an async method to only resume processing on the UI thread if the awaited task wasn't cancelled? Or is this one of the border cases where, due to the frequency of the SizeChanged events, we should not use await, but revert to the old ContinueWith pattern which affords more control (like TaskContinuationOptions.NotOnCanceled)?

Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Check [this](http://stackoverflow.com/q/21611292/1768303). You can use `AsyncOp` from there in a very similar way. – noseratio Sep 26 '15 at 21:59

1 Answers1

4

it should only perform processing when there are no further SizeChanged events for a 500 ms period

As soon as you have a "time" requirement, that's a pretty clear sign you should be using Rx. Something like this should work:

Observable.FromEventPattern<SizeChangedEventHandler, SizeChangedEventArgs>(h => SizeChanged += h, h => SizeChanged -= h)
    .Throttle(TimeSpan.FromMilliseconds(500))
    .ObserveOn(SynchronizationContext.Current)
    .Subscribe(_ =>
    {
       myTextBox.Text = string.Format("({0}, {1})", this.Width, this.Height);
    });
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • In case anyone needs to perform an asynchronous cancellable operation from the callback, refer to [Throttling Search Queries On TextChanged With Cancellation Support](http://kasperholdum.dk/2013/04/throttling-search-queries-on-textchanged-with-cancellation-support/), which is largely identical to the above, but uses `Scan` to provide a fresh `CancellationTokenSource` to the callback. – Douglas Sep 28 '15 at 17:44