24

I need to write some asynchronous code that essentially attempts to repeatedly talk to and initialise a database. Quite often the first attempt will fail hence the requirement for it to retry.

In days of old I would have used a pattern similar to:

void WaitForItToWork()
{
    bool succeeded = false;
    while (!succeeded)
    {
        // do work
        succeeded = outcome; // if it worked, mark as succeeded, else retry
        Threading.Thread.Sleep(1000); // arbitrary sleep
    }
}

I realise a lot of changes have been made recently to .NET with regards to async patterns so my question really is this the best method to use or is it worth while exploring the async stuff and if so how do I implement this pattern in async?

Update

Just to clarify, I want to spawn this work asynchronously so that the method which spawns it does not have to wait for it to finish as it will be spawned in the constructor of a service so the constructor must return instantly.

Chris
  • 26,744
  • 48
  • 193
  • 345

4 Answers4

37

You could refactor that fragment like this:

async Task<bool> WaitForItToWork()
{
    bool succeeded = false;
    while (!succeeded)
    {
        // do work
        succeeded = outcome; // if it worked, make as succeeded, else retry
        await Task.Delay(1000); // arbitrary delay
    }
    return succeeded;
}

Apparently, the only benefit it would give you is more efficient use of thread pool, because it doesn't always take a whole thread to make the delay happen.

Depending on how you obtain outcome, there may be much more efficient ways to get this job done using async/await. Often you may have something like GetOutcomeAsync() which would make a web service, database or socket call asynchronously in a natural way, so you'd just do var outcome = await GetOutcomeAsync().

It's important to take into account that WaitForItToWork will be split into parts by compiler and the part from await line will be continued asynchronously. Here's perhaps the best explanation on how it's done internally. The thing is, usually at some point of your code you'd need to synchronize on the result of the async task. E.g.:

private void Form1_Load(object sender, EventArgs e)
{
    Task<bool> task = WaitForItToWork();
    task.ContinueWith(_ => {
        MessageBox.Show("WaitForItToWork done:" + task.Result.toString()); // true or false
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

You could have simply done this:

private async void Form1_Load(object sender, EventArgs e)
{
    bool result = await WaitForItToWork();
    MessageBox.Show("WaitForItToWork done:" + result.toString()); // true or false
}

That would however make Form1_Load an async method too.

[UPDATE]

Below is my attempt to to illustrate what async/await actually does in this case. I created two versions of the same logic, WaitForItToWorkAsync (using async/await) and WaitForItToWorkAsyncTap (using TAP pattern without async/await). The frist version is quite trivial, unlike the second one. Thus, while async/await is largely the compiler's syntactic sugar, it makes asynchronous code much easier to write and understand.

// fake outcome() method for testing
bool outcome() { return new Random().Next(0, 99) > 50; }

// with async/await
async Task<bool> WaitForItToWorkAsync()
{
    var succeeded = false;
    while (!succeeded)
    {
        succeeded = outcome(); // if it worked, make as succeeded, else retry
        await Task.Delay(1000);
    }
    return succeeded;
}

// without async/await
Task<bool> WaitForItToWorkAsyncTap()
{
    var context = TaskScheduler.FromCurrentSynchronizationContext();
    var tcs = new TaskCompletionSource<bool>();
    var succeeded = false;
    Action closure = null;

    closure = delegate
    {
        succeeded = outcome(); // if it worked, make as succeeded, else retry
        Task.Delay(1000).ContinueWith(delegate
        {
            if (succeeded)
                tcs.SetResult(succeeded);
            else
                closure();
        }, context);
    };

    // start the task logic synchronously
    // it could end synchronously too! (e.g, if we used 'Task.Delay(0)')
    closure();

    return tcs.Task;
}

// start both tasks and handle the completion of each asynchronously
private void StartWaitForItToWork()
{
    WaitForItToWorkAsync().ContinueWith((t) =>
    {
        MessageBox.Show("WaitForItToWorkAsync complete: " + t.Result.ToString());
    }, TaskScheduler.FromCurrentSynchronizationContext());

    WaitForItToWorkAsyncTap().ContinueWith((t) =>
    {
        MessageBox.Show("WaitForItToWorkAsyncTap complete: " + t.Result.ToString());
    }, TaskScheduler.FromCurrentSynchronizationContext());
}

// await for each tasks (StartWaitForItToWorkAsync itself is async)
private async Task StartWaitForItToWorkAsync()
{
    bool result = await WaitForItToWorkAsync();
    MessageBox.Show("WaitForItToWorkAsync complete: " + result.ToString());

    result = await WaitForItToWorkAsyncTap();
    MessageBox.Show("WaitForItToWorkAsyncTap complete: " + result.ToString());
}

A few words on threading. There is no additional threads explicitly created here. Internally, Task.Delay() implementation may use pool threads (I suspect they use Timer Queues), but in this particular example (a WinForms app), the continuation after await will happen on the same UI thread. In other execution environments (e.g. a console app), it might continue on a different thread. IMO, this article by Stephen Cleary is a must-read to understand async/await threading concepts.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • Would I call this simply by doing `this.WaitForItToWork();` - will the async library take care of the threading for me? – Chris Sep 03 '13 at 08:49
  • 1
    You would call it like `await this.WaitForItToWork()`, and the whole chain of calls must be refactored to support this... I'll elaborate on my answer to include more info this. – noseratio Sep 03 '13 at 08:51
  • @Chris: you must remember to use "await" keyword. Rule of thumb: Always "await" must be coupled with "async" function. So, you should do something like await WaitForItToWork(); – now he who must not be named. Sep 03 '13 at 08:51
  • I dont want to await for it, I want to spawn this task and carry on with what I was doing – Chris Sep 03 '13 at 08:55
  • I've just update my answer, hope it makes more sense now. You actually do not *wait* (there is nothing like blocking `Sleep`). The best analogy for `await Task.Delay()` would be, perhaps, a timer event. `async/await` is just a syntactic sugar provided by compiler. In a way it's similar to `node.js` in case you're familiar with it (although Node's javascript lacks the syntax convenience - everything is done through closures - delegates in C#). – noseratio Sep 03 '13 at 09:09
2

If the task is asynchronous you can try with:

    async Task WaitForItToWork()
    {
        await Task.Run(() =>
        {
            bool succeeded = false;
            while (!succeeded)
            {
                // do work
                succeeded = outcome; // if it worked, make as succeeded, else retry
                System.Threading.Thread.Sleep(1000); // arbitrary sleep
            }
        });
    }

See http://msdn.microsoft.com/en-us/library/hh195051.aspx.

Alessandro D'Andria
  • 8,663
  • 2
  • 36
  • 32
  • 1
    This is exactly what I wanted to avoid with my answer. Perhaps, I am myself missing something :] – noseratio Sep 03 '13 at 09:18
  • @Noseratio The async method will return immediately when an await is hit. The c# compiler does all the thread synchronization for you. http://msdn.microsoft.com/en-us/library/vstudio/hh156528.aspx Importantly; "_An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task_" – Gusdor Sep 03 '13 at 09:23
  • I mispoke. Chillax. No harm in correcting or adding clarity, even after the fact. I edited to avoid confusion from other readers. I did not edit to make you look stupid. That would violate the DRY principle. /sickburn – Gusdor Sep 03 '13 at 09:36
  • @Gusdor, understood. I went ahead and deleted my comments as not useful in this context. – noseratio Sep 03 '13 at 09:43
  • I was tempted to remove the `Sleep(1000)` completely. But I simply kept much of the original code how i could. – Alessandro D'Andria Sep 03 '13 at 10:07
  • Removing `Sleep(1000)` would make it a 'busy waiting'. On the other hand, adding `await Task.Delay` instead would make the spawning `Task.Run` redundant, IMO. – noseratio Sep 03 '13 at 10:13
  • 1
    Yes maybe in this scenario the best solution was a Timer, that try every interval and when connect disable it. – Alessandro D'Andria Sep 03 '13 at 10:19
  • 3
    This is a known anti-pattern (http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx). I suggest that Noseratio's answer be accepted because I do not want future visitors to find this as the best solution. – usr Sep 03 '13 at 12:14
2

Just provide another solution

public static void WaitForCondition(Func<bool> predict)
    {
        Task.Delay(TimeSpan.FromMilliseconds(1000)).ContinueWith(_ =>
        {
            var result = predict();
            // the condition result is false, and we need to wait again.
            if (result == false)
            {
                WaitForCondition(predict);
            }
        });
    }
0

You don't really need WaitItForWork method, just await for a database initialization task:

async Task Run()
{
    await InitializeDatabase();
    // Do what you need after database is initialized
}

async Task InitializeDatabase()
{
    // Perform database initialization here
}

If you have multiple pieces of code that call to WaitForItToWork then you need to wrap database initialization into a Task and await it in all workers, for example:

readonly Task _initializeDatabaseTask = InitializeDatabase();

async Task Worker1()
{
    await _initializeDatabaseTask;
    // Do what you need after database is initialized
}

async Task Worker2()
{
    await _initializeDatabaseTask;
    // Do what you need after database is initialized
}

static async Task InitializeDatabase()
{
    // Initialize your database here
}
Konstantin Spirin
  • 20,609
  • 15
  • 72
  • 90