8

I know it is bad to call Task.Wait in UI thread. It causes a deadlock. Please refer to

Constructor invoke async method

Await, and UI, and deadlocks! Oh my!

Take the following code:

    public MainPage()
    {
        this.InitializeComponent();

        step0();
        step1();
    }

    private async Task step0()
    {
        await Task.Delay(5000);
        System.Diagnostics.Debug.WriteLine("step 0");
    }

    private async Task step1()
    {
        await Task.Delay(3000);
        System.Diagnostics.Debug.WriteLine("step 1");
    }
}

How can I ensure that "step 0" is always being printed before "step 1"? Using Task.Wait would cause deadlock. This is Windows Store App

Dave New
  • 38,496
  • 59
  • 215
  • 394
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
  • 1
    Deadlock is when two threads are trying to access data with a lock, so they end up waiting forever – Cole Tobin Nov 16 '12 at 07:17
  • 4
    @AlexeiLevenkov: Actually, [`Wait` *does* cause a deadlock, as I explain in detail on my blog](http://nitoprograms.blogspot.com/2012/07/dont-block-on-async-code.html). – Stephen Cleary Nov 16 '12 at 13:37
  • 1
    @StephenCleary, thanks. Comment about "Wait/Sleep does not cause "deadlock", both cause non-responsive UI" is deleted as wrong - sync completion of async task can cause real deadlock on if requires new thread and reporting back to original one. – Alexei Levenkov Nov 16 '12 at 17:09

2 Answers2

8

You can use Task.ContinueWith to chain the tasks together so that they happen sequentially.

public MainPage()
{
    this.InitializeComponent();

    step0().ContinueWith(t => step1());
}

Also, Alexei's comment is correct. Task.Wait will block the UI thread. Deadlocks are something else.

Dave New
  • 38,496
  • 59
  • 215
  • 394
  • Thanks. But I was curious. If `step0().ContinueWith(delegate{ step1(); });` is valid, why `step0().ContinueWith(() => step1());` doesn't valid? – Cheok Yan Cheng Nov 16 '12 at 07:59
  • As `fun(()=>System.Diagnostics.Debug.WriteLine("hello"));` and `fun(delegate { System.Diagnostics.Debug.WriteLine("hello"); });` both workable. – Cheok Yan Cheng Nov 16 '12 at 08:00
  • 1
    [Answer to your question](http://stackoverflow.com/questions/505850/can-i-ignore-delegate-parameters-with-lambda-syntax) – Dave New Nov 16 '12 at 08:02
5

You can't have an async constructor, period.

So whatever you do, you'll need to handle the situation where your constructor returns without having completed its async initialization.

There are a few ways of handling this. If the "steps" are loading resources, then you can do something like this (using the AsyncLazy type from my blog, Stephen Toub's blog, or my AsyncEx library - they're all nearly the same):

private readonly AsyncLazy<MyResource> resource;

public MainPage()
{
  resource = new AsyncLazy<MyResource>(async () =>
  {
    var a = await step0();
    var b = await step1();
    return new MyResource(a, b); // or whatever
  });
  resource.Start(); // start step0
}

public async Task MethodThatNeedsResource()
{
  var r = await resource; // ensure step1 is complete before continuing
}

It's also possible to do this with an async void method or ContinueWith, but you'll have to carefully consider error handling with those approaches.

  • The AsyncLazy approach will capture errors and raise them whenever await resource is executed.
  • The async void approach will throw errors immediately to the SynchronizationContext.
  • The naive ContinueWith approach will silently ignore all errors.

Whichever approach you take, I recommend having some notification in the UI that the initialization is in progress. (And as a side note, I think all of this should go into a ViewModel or Model class rather than MainPage).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810