27

I would like to write a method that will await for a variable to be set to true.

Here is the psudo code.

bool IsSomethingLoading = false
SomeData TheData;

public async Task<SomeData> GetTheData()
{
   await IsSomethingLoading == true;
   return TheData;
}

TheData will be set by a Prism Event along with the IsSomethingLoading variable.

I have a call to the GetTheData method, but I would like it to run async (right now it just returns null if the data is not ready. (That leads to other problems.)

Is there a way to do this?

Vaccano
  • 73,620
  • 138
  • 433
  • 790

4 Answers4

25

In many situations like this what you need is a TaskCompletionSource.

You likely have a method that is able to generate the data at some point in time, but it doesn't use a task to do it. Perhaps there is a method that takes a callback which provides the result, or an event that is fired to indicate that there is a result, or simply code using a Thread or ThreadPool that you are not inclined to re-factor into using Task.Run.

public Task<SomeData> GetTheData()
{
    TaskCompletionSource<SomeData> tcs = new TaskCompletionSource<SomeData>();
    SomeObject worker = new SomeObject();
    worker.WorkCompleted += result => tcs.SetResult(result);
    worker.DoWork();
    return tcs.Task;
}

While you may need/want to provide the TaskCompletionSource to the worker, or some other class, or in some other way expose it to a broader scope, I've found it's often not needed, even though it's a very powerful option when it's appropriate.

It's also possible that you can use Task.FromAsync to create a task based on an asynchronous operation and then either return that task directly, or await it in your code.

Servy
  • 197,813
  • 25
  • 319
  • 428
18

You could use a TaskCompletionSource as your signal, and await that:

TaskCompletionSource<bool> IsSomethingLoading = new TaskCompletionSource<bool>();
SomeData TheData;

public async Task<SomeData> GetTheData()
{
   await IsSomethingLoading.Task;
   return TheData;
}

And in your Prism event do:

IsSomethingLoading.SetResult(true);
Bort
  • 7,250
  • 3
  • 30
  • 48
3

This work for me:

while (IsLoading) await Task.Delay(100);
0

I propose a very simple solution but not the best to answer the original question, if you are not regarding at speed performance :

...
public volatile bool IsSomethingLoading = false;
...
public async Task<SomeData> GetTheData()
{
    // Launch the task asynchronously without waiting the end
    _ = Task.Factory.StartNew(() =>
    {
        // Get the data from elsewhere ...
    });

    // Wait the flag    
    await Task.Factory.StartNew(() =>
    {
        while (IsSomethingLoading)
        {
            Thread.Sleep(100);
        }
    });

   return TheData;
}

Important note : @Theodor Zoulias proposed : IsSomethingLoading shall be declared with volatile keyword, to avoid compiler optimizations and potential multithread issues when accessing from other threads. For further information about compilator omptimizations follow this article : The C# Memory Model in Theory and Practice

I'm adding a full test code below :

XAML :

<Label x:Name="label1" Content="Label" HorizontalAlignment="Left" Margin="111,93,0,0" VerticalAlignment="Top" Grid.ColumnSpan="2" Height="48" Width="312"/>

Test Code :

public partial class MainWindow : Window
{
    // volatile keyword shall be used to avoid compiler optimizations
    // and potential multithread issues when accessing IsSomethingLoading
    // from other threads.
    private volatile bool IsSomethingLoading = false;

    public MainWindow()
    {
        InitializeComponent();

        _ = TestASyncTask();
    }

    private async Task<bool> TestASyncTask()
    {
        IsSomethingLoading = true;

        label1.Content = "Doing background task";

        // Launch the task asynchronously without waiting the end
        _ = Task.Factory.StartNew(() =>
        {
            Thread.Sleep(2000);
            IsSomethingLoading = false;
            Thread.Sleep(5000);
            HostController.Host.Invoke(new Action(() => label1.Content = "Background task terminated"));
        });
        label1.Content = "Waiting IsSomethingLoading ...";

        // Wait the flag    
        await Task.Run(async () => { while (IsSomethingLoading) { await Task.Delay(100); }});
        label1.Content = "Wait Finished";

        return true;
    }

}

/// <summary>
/// Main UI thread host controller dispatcher
/// </summary>
public static class HostController
{
    /// <summary>
    /// Main Host
    /// </summary>
    private static Dispatcher _host;
    public static Dispatcher Host
    {
        get
        {
            if (_host == null)
            {
                if (Application.Current != null)
                    _host = Application.Current.Dispatcher;
                else
                    _host = Dispatcher.CurrentDispatcher;
            }

            return _host;
        }
    }
}