web-dev-qa-db-de.com

Beste asynchrone while-Methode

Ich muss etwas asynchronen Code schreiben, der im Wesentlichen versucht, wiederholt mit einer Datenbank zu sprechen und sie zu initialisieren. Häufig schlägt der erste Versuch fehl, daher muss er erneut versucht werden.

In früheren Zeiten hätte ich ein ähnliches Muster verwendet:

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

Ich habe festgestellt, dass in letzter Zeit viele Änderungen an .NET bezüglich asynchroner Muster vorgenommen wurden. Meine Frage ist also, ob dies die beste Methode ist, oder lohnt es sich, das async-Material zu erkunden, und wenn ja, wie implementiere ich dieses Muster in async?

Update

Zur Verdeutlichung möchte ich diese Arbeit asynchron erzeugen, damit die Methode, die sie erzeugt, nicht warten muss, bis sie beendet ist, da sie im Konstruktor eines Dienstes erzeugt wird, so dass der Konstruktor sofort zurückkehren muss.

19
Chris

Sie könnten dieses Fragment folgendermaßen umwandeln:

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

Anscheinend ist der einzige Vorteil, den es für Sie bietet, die effizientere Nutzung des Thread-Pools, da nicht immer ein ganzer Thread benötigt wird, um die Verzögerung zu bewirken.

Je nachdem, wie Sie outcome erhalten, können Sie mit async/await diese Aufgabe effizienter erledigen. Oft haben Sie etwas wie GetOutcomeAsync(), das einen Webservice-, Datenbank- oder Socket-Aufruf auf natürliche Weise asynchron veranlassen würde, so dass Sie einfach var outcome = await GetOutcomeAsync() ausführen würden. 

Es ist wichtig zu berücksichtigen, dass WaitForItToWork vom Compiler in Teile aufgeteilt wird und der Teil aus await-Zeile asynchron fortgesetzt wird. Hier ist vielleicht die beste Erklärung, wie es intern gemacht wird. Das Problem ist, dass Sie normalerweise an einem bestimmten Punkt Ihres Codes das Ergebnis der asynchronen Task synchronisieren müssen. Z.B.:

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

Sie hätten das einfach tun können:

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

Dies würde jedoch Form1_Load zu einer asynchronen Methode machen. 

[UPDATE]

Nachfolgend versuche ich zu veranschaulichen, was async/await in diesem Fall tatsächlich tut. Ich habe zwei Versionen derselben Logik erstellt, WaitForItToWorkAsync (mit async/await) und WaitForItToWorkAsyncTap (mit TAP-Muster ohne async/await). Die erste Version ist im Gegensatz zur zweiten ziemlich trivial. Während async/await größtenteils der syntaktische Zucker des Compilers ist, macht er asynchronen Code viel einfacher zu schreiben und zu verstehen.

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

Einige Wörter zum Einfädeln . Es werden hier keine zusätzlichen Threads explizit erstellt. Intern kann die Task.Delay()-Implementierung Pool-Threads verwenden (ich vermute, dass sie Timer Queues verwenden), aber in diesem speziellen Beispiel (einer WinForms-App) wird die Fortsetzung nach await auf demselben UI-Thread ausgeführt. In anderen Ausführungsumgebungen (z. B. einer Konsolenanwendung) kann es mit einem anderen Thread fortgesetzt werden. IMO, dieser Artikel von Stephen Cleary ist ein Muss, um async/await-Threading-Konzepte zu verstehen.

27
noseratio

Wenn die Aufgabe asynchron ist, können Sie Folgendes versuchen:

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

Siehe http://msdn.Microsoft.com/de-de/library/hh195051.aspx .

1

Geben Sie einfach eine andere Lösung an

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

Sie brauchen die WaitItForWork-Methode nicht wirklich, sondern warten nur auf eine Datenbankinitialisierungsaufgabe:

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

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

Wenn Sie über mehrere Codeteile verfügen, die WaitForItToWork aufrufen, müssen Sie die Datenbankinitialisierung in eine Task umschließen und in allen Workern darauf warten. Beispiel:

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