0

I have a constructor that is called on UI thread in my WPF app. Inside of it, it calls async method but it must be done in a synchronous way. So I tried to call wait, but it caused deadlock an I understand why. So I introduced argument that indicates if method should be run in a asynchronous or synchronous manner. Something like this:

// constructor that is called on UI thread
public MyClass()
{
    Method1(false).Wait();
}

public async Task Method1(bool runAsync)
{
    await Method2(runAsync);
}

public async Task Method2(bool runAsync)
{
    if (runAsync)
    {
        await Task.Run(() => Thread.Sleep(1000));
    }
    else
    {
        Thread.Sleep(1000);
    }
}

I don't want to use ConfigureAwait because I want everything to run on UI thread. Will Method1(false).Wait(); ever cause a deadlock (is it safe to use)? I tested it a lot and it didn't, but I'm not sure. Finally, my real question is: if 'await Task.Run(...' is never executed, is my method completely synchronous? I found several posts on this subject, but none of them answers directly to my question.

  • 1
    why can't you have a synchronus version of the same method `an overload probably) which you can use in this case? – Rahul Feb 28 '19 at 08:07
  • I don't want to duplicate the code. This is very simplified version of my problem. – Frodo Baggins Feb 28 '19 at 08:10
  • 1
    Actually you are already duplicating (most probably) with that `if .. else` – Rahul Feb 28 '19 at 08:11
  • That's the trade-off I can handle :) – Frodo Baggins Feb 28 '19 at 08:12
  • Well then I don't see how it can result in a deadblock at all since there is different execution path – Rahul Feb 28 '19 at 08:14
  • I think a deadlock could still happen and to be on the safe side I would just write `public Task Method1(bool runAsync) { return Method2(runAsync); }` and forego the await completely which would be responsible for a deadlock when it tries to switch back to original context. Also in Method2 you also just return the Task.Run task and for your synchronous "else" just do `return Task.CompletedTask;`. – ckuri Feb 28 '19 at 08:18
  • Thank you, Rahul. Ckuri interesting solution, I'll keep in mind if problem occurs. Thanks – Frodo Baggins Feb 28 '19 at 08:27
  • Stop trying to do async stuff at construction. It doesn't belong there and is forcing you to write blocking code (bad). Instead, create an async factory method which news up a purely synchronous constructor with no waiting and then initializes the resulting instance asynchronously, then returns it to the caller. Now you don't have to think about sync/async switching. – spender Feb 28 '19 at 15:33

2 Answers2

0

I have a constructor that is called on UI thread in my WPF app. Inside of it, it calls async method but it must be done in a synchronous way.

I'm gonna stop you right there. The best solution is not to run synchronous/blocking code on the UI thread. That degrades your user experience. Instead, you should restructure your code so that you're never in this situation in the first place.

When a UI is being shown, the UI framework asks your code for the data to display. Your code should run synchronously and return immediately (not blocking). But your code also needs to do some asynchronous work in order to have the data to display. So there's a conflict there.

The solution is to design an intermediate state for your UI, e.g., a "loading..." message or a spinner. Then, when your code starts, it can synchronously/immediately display the "loading" state (and start the asynchronous operation), and when the asynchronous data arrives, the code updates the UI to the "final" state.

I discuss this pattern in more detail in my article on async MVVM data.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • The guy whose answer I was hoping for :) Thank you. I am actually using something like that. I am just one of many developers working on a huge app consisted of over 500 modules (lots of legacy code). We had a problem where call to a service from a UI thread got stuck, message queue was flooded and app crashed. So I made that call async combined with some fancy, common "waiting" dialog. But that call also must be done synchronously during app startup (sort of splash screen is shown) and I cannot use that fancy, common dialog. – Frodo Baggins Mar 03 '19 at 20:44
  • And if it is not problem - can you answer me: can my code cause a deadlock (if we ignore for a second the fact its not recommended way to use async)? – Frodo Baggins Mar 03 '19 at 21:12
  • @FrodoBaggins: If you need to support both sync and async callers, I recommend the [boolean argument hack](https://msdn.microsoft.com/en-us/magazine/mt238404.aspx). If this is a service call, I'd expect you should be able to use naturally asynchronous APIs rather than `Task.Run`. – Stephen Cleary Mar 04 '19 at 09:27
  • I'm planning to introduce asynchronous APIs, but at this moment it's not the case. – Frodo Baggins Mar 04 '19 at 12:28
0

Instead of wrestling with async stuff at construction (your blocking solution isn't so good), why not write an async factory to spew out these objects?

class MyClass
{
    public MyClass()
    {

    }

    public async Task Method2(bool runAsync)
    {
        //async immediately
        await Task.Delay(1000); //no Thread.Sleep. Blocking code != good
    }
}

then

public MyClassFactory
{
    public async Task<MyClass> GetAsync()
    {
        var c = new MyClass();
        await c.Method2();
        return c;
    }
}
spender
  • 117,338
  • 33
  • 229
  • 351
  • Interesting solution. Thank you. But, I have a lot of legacy code that is rarely changed and i needed a way to make my async method call synced (see my reply to a Stephens answer - I would have to make MyClassFactory.GetAsync call also synchronous) and without lot of changes. Now you can tell me that there is a fast way and a good way to do things, but maintaining a huge app with hundreds of other developers sometimes makes you cut the corners. Can you explain me why my blocking solution isn't so good? – Frodo Baggins Mar 03 '19 at 21:04