4

I have a Singleton (well, it can be a static class, doesn't matter), which is a facade for some data for my WPF application. I want to load this data async via WCF. Here is my implementation:

public class Storage
{
    private static readonly Lazy<Storage> _lazyInstance = new Lazy<Storage>(()=>new Storage());

    public static Storage Instance
    {
        get { return _lazyInstance.Value; }
    }

    private Storage()
    {
        Data = new Datastorage(SettingsHelper.LocalDbConnectionString);
        InitialLoad().Wait();
    }

    public Datastorage Data { get; private set; }

    private async Task InitialLoad()
    {
        var tasks = new List<Task>
        {
            InfoServiceWrapper.GetSomeData()
            .ContinueWith(task => Data.StoreItem(task.Result)),
            InfoServiceWrapper.GetAnotherData()
            .ContinueWith(task => Data.StoreItem(task.Result)),
            InfoServiceWrapper.GetSomeMoreData()
            .ContinueWith(task => Data.StoreItem(task.Result)),
        };
        await Task.WhenAll(tasks.ToArray());
    }
}

And i access this class from my ViewModel like this:

public class MainWindowViewModel:ViewModelBase
{
    public  SensorDTO RootSensor { get; set; }
    public  MainWindowViewModel()
    {
        var data = Storage.Instance.Data.GetItem<SensorDTO>(t=>t.Parent==t);
        RootSensor = data;
    }
}

In my view i have a binding for RootSensor. Everything works great, but i have one problem: all my async code executes and then i catch a deadlock on InitialLoad().Wait(); . I understand that it involves WPF UI thread somehow, but don't understand how to fix this.

I'll be gratefull for any help!

Alex Voskresenskiy
  • 2,143
  • 2
  • 20
  • 29
  • I don't have time to give a complete answer at the moment but these two resources should help: http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx – Jason Boyd Apr 13 '15 at 17:27
  • @johnnymumble well, i think i understand what is shown in these papers, but i don't get how to await correctly in my ViewModel constructor. Do i use SynchronizationContext or dispatching somehow? – Alex Voskresenskiy Apr 13 '15 at 17:32
  • Have a look at this: http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html – Paulo Morgado Apr 13 '15 at 21:16

2 Answers2

9

You've basically come up against a limitation in async/await: Constructors cannot be marked async. The right way to solve that isn't by calling Wait from the constructor. That's cheating - it'll block, rendering all your nice asynchrony moot, and what's worse is it'll invite deadlocks as you discovered.

The right way to do it is to refactor your Storage class to ensure all of its async work is done from an async method rather than a constructor. I'd suggest doing this by replacing your Instance property with a GetInstanceAsync() method. As this is the only public interface to getting the singleton instance, you'll ensure that InitialLoad (which I'd rename InitialLoadAsync) will always get called.

public class Storage
{
    private static Storage _instance;

    public static async Task<Storage> GetInstanceAsync()
    {
        if (_instance == null)
        {
            // warning: see comments about possible thread conflict here
            _instance = new Storage();
            await _instance.InitialLoadAsync();
        }
        return _instance;
    }

    private Storage() 
    {
        Data = new Datastorage(SettingsHelper.LocalDbConnectionString);
    }

    // etc

 }

Now, how do you call Storage.GetInstanceAsync() from MainWindowViewModel's constructor without blocking? As you've probably guessed, you can't, so you'll need to refactor that similarly. Something like:

public class MainWindowViewModel : ViewModelBase
{
    public  SensorDTO RootSensor { get; set; }

    public async Task InitializeAsync()
    {
        var storage = await Storage.GetInstanceAsync()
        RootSensor.Data.GetItem<SensorDTO>(t=>t.Parent==t);
    }
}

And of course whatever calls await MainWindowViewModel.InitializeAsync() needs to be marked async as well. async/await are said to spread like a zombie virus through your code, and that's natural. If anywhere along the call stack you've broken that cycle with a .Wait() or .Result, you've invited trouble.

Todd Menier
  • 37,557
  • 17
  • 150
  • 173
  • 1
    One possible unintended consequence I see with this implementation is that multiple threads could call `GetInstanceAsync` before the instance is initialized (i.e. `_instance == null` evaluates to true) leading to multiple calls to `_instance.InitialLoadAsync()` which may or may not be problematic depending on the implementation of `InitialLoadAsync()`. Actually, it would also be problematic because each task could return a different instance of `Storage` and not necessarily the winning instance that ultimately gets stored in the private variable. – Jason Boyd Apr 13 '15 at 19:11
  • Good catch. An [async lazy](http://blog.stephencleary.com/2012/08/asynchronous-lazy-initialization.html) would be useful to avoid thread conflicts here while avoiding blocking. Noted in the code sample. – Todd Menier Apr 13 '15 at 19:18
  • @ToddMenier thank you much for your answer! I still don't understand 2 things: first, who'll call InitializeAsync and second, if i'll use AsyncLazy (which looks promising), do i need to use InitialLoadAsync as continuation for it's task and how it'll work in multithread environment? – Alex Voskresenskiy Apr 14 '15 at 07:35
  • @AlexVoskresenskiy About first thing: you can await `InitializeAsync` inside the async Window_Loaded event handler. About second thing: you can have something like `private static AsyncLazy _lazyInstance = new AsyncLazy(async () => { var st = new Storage(); await st.InitialLoad(); return st; }); public static async Storage GetInstance() { get { var ins = await _lazyInstance; return ins; } }` . And it should work well in multithreaded environment (if all async-await practices are properly followed). – Eugene Podskal Apr 14 '15 at 08:39
1

Solution 1 - What if you do not wait at all?

If you wait for some task in the constructor then your app won't launch till it gets the data. Thus the launch time of the app is increased and UX will be somewhat less satisfying.

But if you just set some dummy default data and launch the data retrieval fully async without wait or await, you will have no need to Wait and improve the overall UX. To prevent any unnecessary operations by user you can either make dependent controls disabled or use Null object pattern:

public class Waiter : INotifyPropertyChanged
{
    public async Task<String> Get1()
    {
        await Task.Delay(2000);            
        return "Got value 1";
    }

    public async Task<String> Get2()
    {
        await Task.Delay(3000);
        return "Got value 2";
    }

    private void FailFast(Task task)
    {
        MessageBox.Show(task.Exception.Message);
        Environment.FailFast("Unexpected failure");
    }

    public async Task InitialLoad()
    {          
        this.Value = "Loading started";

        var task1 = Get1();
        var task2 = Get2();

        // You can also add ContinueWith OnFaulted for task1 and task2 if you do not use the Result property or check for Exception
        var tasks = new Task[]
        {
            task1.ContinueWith(
                (prev) => 
                    this.Value1 = prev.Result),
            task2.ContinueWith(
                (prev) => 
                    this.Value2 = prev.Result)
        };

        await Task.WhenAll(tasks);

        this.Value = "Loaded";
    }

    public Waiter()
    {
        InitialLoad().ContinueWith(FailFast, TaskContinuationOptions.OnlyOnFaulted);
    }

    private String _Value,
        _Value1,
        _Value2;

    public String Value
    {
        get
        {
            return this._Value;
        }
        set
        {
            if (value == this._Value)
                return;
            this._Value = value;
            this.OnPropertyChanged();
        }
    }


    public String Value1
    {
        get { return this._Value1; }
        set
        {
            if (value == this._Value1)
                return;
            this._Value1 = value;
            this.OnPropertyChanged();
        }
    }


    public String Value2
    {
        get { return this._Value2; }
        set
        {
            if (value == this._Value2)
                return;
            this._Value2 = value;
            this.OnPropertyChanged();
        }
    }

    public void OnPropertyChanged([CallerMemberName]String propertyName = null)
    {
        var propChanged = this.PropertyChanged;

        if (propChanged == null)
            return;

        propChanged(this, new PropertyChangedEventArgs(propertyName));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = new Waiter();          
    }
}

XAML:

    <StackPanel>
        <TextBox Text="{Binding Value}"/>
        <TextBox Text="{Binding Value1}"/>
        <TextBox Text="{Binding Value2}"/>
    </StackPanel>

Important warning: As it has been pointed by @YuvalItzchakov originally posted solution will silently miss any exceptions that can occur in the async method, so you will have to wrap your async method bodies with some try-catch logic calling Environment.FailFast to fail fast and loud or use appropriate ContinueWith with TaskContinuationOptions.OnlyOnFaulted.

Bad solution 2(!May actually fail!) - ConfigureAwait(false)

Configure await false on all async calls will prevent(in most cases) any use of the original sync context allowing you to wait:

   public async Task<String> Get1()
    {
        await Task.Delay(2000).ConfigureAwait(false);            
        return "Got value 1";
    }

    public async Task<String> Get2()
    {
        await Task.Delay(3000).ConfigureAwait(false);
        return "Got value 2";
    }

    public async Task InitialLoad()
    {          
        this.Value = "Loading started";

        var tasks = new Task[]
        {
            Get1().ContinueWith(
                (prev) => 
                    this.Value1 = prev.Result),
            Get2().ContinueWith(
                (prev) => 
                    this.Value2 = prev.Result)
        };

        await Task.WhenAll(tasks).ConfigureAwait(false);

        this.Value = "Loaded";
    }

    public Waiter()
    {
        InitialLoad().Wait();
    }

It will work in most cases, but it is not actually guaranteed that it won't use the same thread to await leading to the same deadlock problem.

Bad solution 3 - use Task.Run to assuredly avoid any deadlocking.

You can use one not very good async practice and wrap your entire operation into new thread pool task with Task.Run:

private void SyncInitialize()
{
    Task.Run(() => 
                 InitialLoad().Wait())
        .Wait();
}

It will squander one thread from the thread pool on wait, but it will work for sure, while Solution 2 may fail.

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • This implementation may both deadlock and miss any exception happening inside it's `InitialLoad` method. I wouldn't use it. – Yuval Itzchakov Apr 13 '15 at 19:18
  • @YuvalItzchakov Solution 1 as I know will not be able to deadlock, because there are no waits? With solution 2..., yes, you are right, it may theoretically deadlock, because `ConfigureAwait` do not really guarantee that it won't await it on the same thread thus deadlocking. – Eugene Podskal Apr 13 '15 at 19:24
  • @YuvalItzchakov I have added info about the problematic moments you had pointed me to. Thank you. – Eugene Podskal Apr 13 '15 at 19:55