web-dev-qa-db-fra.com

Meilleure méthode while asynchrone

J'ai besoin d'écrire du code asynchrone qui essaie essentiellement de parler et d'initialiser de manière répétée une base de données. Très souvent, la première tentative échouera, d'où la nécessité de réessayer.

Dans les temps anciens, j'aurais utilisé un modèle semblable à:

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
    }
}

Je me rends compte que de nombreux changements ont été apportés récemment à .NET en ce qui concerne les modèles asynchrones. Ma question est-elle vraiment la meilleure méthode à utiliser ou vaut-il la peine d'explorer les éléments async et si oui, comment puis-je implémenter ce modèle dans async?

Mettre à jour

Juste pour clarifier, je veux créer ce travail de manière asynchrone afin que la méthode qui l'engendre ne soit pas obligée d'attendre la fin, car elle sera générée dans le constructeur d'un service, de sorte que le constructeur doit revenir immédiatement.

19
Chris

Vous pouvez refactoriser ce fragment comme ceci:

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;
}

Apparemment, le seul avantage qu'il vous apporterait serait une utilisation plus efficace du pool de threads, car il ne faut pas toujours un thread entier pour que le délai prenne fin.

En fonction de la manière dont vous obtenez outcome, il peut exister des moyens beaucoup plus efficaces d'effectuer ce travail à l'aide de async/await. Il arrive souvent que vous ayez quelque chose comme GetOutcomeAsync() qui permet à un service Web, à une base de données ou à un socket d’appeler de manière naturelle de manière asynchrone. Il vous suffit donc de faire var outcome = await GetOutcomeAsync()

Il est important de prendre en compte que WaitForItToWork sera divisé en parties par le compilateur et que la partie de la ligne await sera poursuivie de manière asynchrone. Voici peut-être la meilleure explication sur la façon dont cela se fait en interne. Le problème est que vous devez généralement synchroniser le résultat de la tâche asynchrone à un moment donné de votre code. Par exemple.:

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());
}

Vous auriez pu simplement faire ceci:

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

Cela ferait cependant de Form1_Load une méthode async. 

[UPDATE]

Voici ma tentative pour illustrer ce que async/await fait réellement dans ce cas. J'ai créé deux versions de la même logique, WaitForItToWorkAsync (avec async/await) et WaitForItToWorkAsyncTap (avec modèle TAP sans async/await). La première version est assez triviale, contrairement à la seconde. Ainsi, alors que async/await est en grande partie le sucre syntaxique du compilateur, il rend le code asynchrone beaucoup plus facile à écrire et à comprendre.

// 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());
}

Quelques mots sur le filetage . Il n'y a pas de threads supplémentaires créés explicitement ici. En interne, l'implémentation Task.Delay() peut utiliser des threads de pool (je suppose qu'ils utilisent Files d'attente de minuteur ), mais dans cet exemple particulier (une application WinForms), la continuation après await aura lieu sur le même thread d'interface utilisateur. Dans d'autres environnements d'exécution (par exemple, une application de console), il se peut que le processus se poursuive sur un autre thread. IMO, cet article de Stephen Cleary est une lecture indispensable pour comprendre les concepts de threading async/await.

27
noseratio

Si la tâche est asynchrone, vous pouvez essayer avec:

    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
            }
        });
    }

Voir http://msdn.Microsoft.com/en-us/library/hh195051.aspx .

1

Juste fournir une autre 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);
            }
        });
    }
1
user2986287

Vous n'avez pas vraiment besoin de la méthode WaitItForWork, attendez simplement une tâche d'initialisation de la base de données:

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

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

Si vous avez plusieurs morceaux de code appelant WaitForItToWork, vous devez envelopper l'initialisation de la base de données dans une Task et l'attendre dans tous les travailleurs, par exemple:

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
}
0
Konstantin Spirin