1

I'm trying to prevent freezing application interface at waiting moment, so I code this function, but it doesn't work.

Where is the mistake, and how can I solve this?

async void buttonBlocking()
{
    await Task.Run(() =>
    {
        if (irTryCounter % 3 == 0)
        {
            this.Dispatcher.Invoke(() =>
            {
                grdLogin.IsEnabled = false;
                Thread.Sleep(10000);
                grdLogin.IsEnabled = true;
            });
        }
    });
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Recep Gunes
  • 157
  • 1
  • 8
  • 1
    you can as well use `async/await` in lambda for invoke with `Task.Delay`, but that's weird. Use timer? – Sinatr Jun 17 '21 at 10:11
  • 6
    `this.Dispatcher.Invoke()` will run that code in the context of the UI thread, so the sleep will be stopping the UI thread. – Matthew Watson Jun 17 '21 at 10:12
  • Also [related](https://stackoverflow.com/q/45447955/1997232). – Sinatr Jun 17 '21 at 10:18
  • 1
    In addition to @MatthewWatson's fine comment, please note that `Dispatcher.Invoke` can lead to deadlock not only for the UI thread but also for the thread pool thread. For this reason consider using `Dispatcher.BeginInvoke`. Of course you will want to move that `Thread.Sleep` elsewhere –  Jun 17 '21 at 11:14

2 Answers2

2

If your buttonBlocking is invoked from UI thread, then you can simplify your code:

async void buttonBlocking()
{
  if (irTryCounter % 3 == 0)
  {
    grdLogin.IsEnabled = false;
    await Task.Delay(10000);
    grdLogin.IsEnabled = true;
  }
}

This way it won't block your UI

Quercus
  • 2,015
  • 1
  • 12
  • 18
-1

The mistake, as pointed by Matthew Watson in a comment, is that the Dispatcher.Invoke() will run the code on the UI thread, so the Thread.Sleep will be blocking the UI thread.

Now let's see how this problem can be fixed. I am assuming that your code is an event handler, and you want this handler to run asynchronously. This is the only valid scenario for using an async void method. In any other case you should use async Task.

private async void Button1_Click(object sender, RoutedEventArgs e)
{
    if (irTryCounter % 3 == 0)
    {
        grdLogin.IsEnabled = false;
        await Task.Run(() =>
        {
            // Here you can run any code that takes a long time to complete
            // Any interaction with UI controls is forbidden here
            // This code will run on the thread-pool, not on the UI thread
            Thread.Sleep(10000);
        });
        grdLogin.IsEnabled = true;
    }
}

In case the long-running operation returns a result that needs to be passed to the UI thread, use the Task.Run overload that returns a Task<TResult>:

int result = await Task.Run(() =>
{
    Thread.Sleep(10000);
    return 13;
});
// We are back on the UI thread, and the result is available for presentation

I am assuming that the Thread.Sleep(10000) is a placeholder for an actual synchronous operation that takes a long time to complete. It would be even better if this operation can be performed asynchronously, because it would not require consuming a ThreadPool thread for the whole duration of the operation. As an example, a delay can be performed asynchronously by using the Task.Delay method:

await Task.Run(async () =>
{
    await Task.Delay(10000);
});

In this case the Task.Run wrapper may seem redundant, but actually it's not. Wrapping your asynchronous code in a Task.Run protects your application from badly behaving asynchronous APIs that block the current thread. The overhead of including the wrapper is minuscule, and should have no noticeable effect in a WinForms/WPF application. The exception is ASP.NET applications, where Task.Runing is inadvisable.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    Thanks for updating your answer, I think it reads much better now with the detailed explanation and I particularly like the walkthrough style.. :) –  Jun 19 '21 at 07:52