Manity
Manity

Reputation: 47

WPF how to await code inside controls events

I am using a 3rd party library that i cannot change how it functions. It is a lib wrapper that allows me to adjust video filters such as brightness and contrast on video files using the FFMPEG lib.

I am trying to use a slider/trackbar in WPF to provide a slider to adjust the contrast level to this lib I have this code

 private async void TbeVolumeLevel_EditValueChanged(object sender, 
 DevExpress.Xpf.Editors.EditValueChangedEventArgs e)
    {
      bool x = await _PlayerList[0].UpdateVideoFilter("eq=contrast="+ 
      e.NewValue.ToString());

    }

public async Task<bool> UpdateVideoFilter(string sFilter)
  {           
    _Filter = sFilter;
    x = await MEMediaPlayer.ChangeMedia();
       return x;
  }

this works briefly (i can see the contrast of the video change) before the application just closes with no errors or exceptions. I have enabled all exceptions in VS and nothing gets trapped.

"ChangeMedia();" is a function in the 3rd party lib and it seems from my testing that calling it too many times before it completes its function is causing the issue. My best guess is there is something like overflow situation occurring causing the application to shut down.

But if i use this code below i can call as much as i like in rapid execution with no problem.

private async void BtnPlay_Click(object sender, RoutedEventArgs e)
    {
     for (int i = 1; i < 20; i++)
        {
            foreach (FFMEBaseVideoPlayer player in _PlayerList)
            {
                var x = await player.UpdateVideoFilter("eq=contrast=" + (i/10));
              Debug.WriteLine(i);
            }
        }
    }

this tells me calling player.UpdateVideoFilter from inside the slider control event it does not await the call to the function but makes concurrent calls with every change of the slider value causing it to crash

So how can i do this. How do i provide a good user experience where they can smoothly change values using the slider control, in a manner the user would expect.

I need to find a way to make code called in a control event await the call to await player.UpdateVideoFilter before the next execution.

The time taken for the await to return is not significant enough as far as i can see to block the slider control function. I just need it to await.

Any advice appreciated.

Upvotes: 0

Views: 240

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456322

The problem is that event handlers are async void methods, which cannot be awaited. WPF (and other UI frameworks) will allow as many event handlers to fire as the user requests.

For some UI elements - such as buttons - the standard approach is to disable the button at the beginning of the handler and re-enable it at the end. This is an easy way to prevent multiple asynchronous handlers from running simultaneously.

In your example, where the user can change the slider a lot over a short period of time, I would recommend using Channels. Channels are like producer/consumer queues that can be bounded and have built-in logic for how to handle when too many items come in at once ("back pressure").

So you could have a bounded channel of size 1 that discards older entries, like this:

private readonly Channel<string> _contrastValue = Channel.CreateBounded<string>(new BoundedChannelOptions
{
  Capacity = 1,
  FullMode = BoundedChannelFullMode.DropOldest,
});

// You'll need to start this consumer somewhere and observe it (via await) to ensure you see exceptions
private async Task ConsumeContrastValueAsync()
{
  var reader = _contrastValue.Reader;
  while (await reader.WaitToReadAsync(CancellationToken.None))
    while (reader.TryRead(out var value))
      await _PlayerList[0].UpdateVideoFilter("eq=contrast=" + value);
}

private async void TbeVolumeLevel_EditValueChanged(object sender, DevExpress.Xpf.Editors.EditValueChangedEventArgs e)
{
  await _contrastValue.Writer.WriteAsync(e.NewValue.ToString(), CancellationToken.None);
}

Upvotes: 2

Related Questions