簡體   English   中英

最佳異步while方法

[英]Best asynchronous while method

我需要編寫一些異步代碼,這些代碼本質上是試圖反復與數據庫進行對話和初始化。 通常,第一次嘗試將失敗,因此需要重試。

在過去的日子里,我會使用類似於以下的模式:

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

我意識到最近在.NET方面對異步模式進行了很多更改,因此我的問題確實是這是使用的最佳方法,還是在探索async內容時是否值得,如果是的話,如何在async實現此模式?

更新資料

為了澄清起見,我想異步生成該工作,以便生成它的方法不必等待它完成,因為它將在服務的構造函數中產生,因此構造函數必須立即返回。

您可以像這樣重構該片段:

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

顯然,它將給您帶來的唯一好處是更有效地使用線程池,因為它並不總是需要整個線程來使延遲發生。

根據獲得outcome ,可能會有更有效的方法來使用async/await完成此工作。 通常,您可能會遇到類似GetOutcomeAsync() ,該問題會以自然方式異步進行Web服務,數據庫或套接字的調用,因此您只需要執行var outcome = await GetOutcomeAsync()

重要的是要考慮到, WaitForItToWork將由編譯器拆分為多個部分,而來自await行的部分將異步繼續。 也許是對其內部完成方式的最佳解釋。 問題是,通常在代碼的某些點上,您需要根據異步任務的結果進行同步。 例如:

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

您可以簡單地做到這一點:

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

但是,那也會使Form1_Load成為異步方法。

[更新]

下面是我嘗試說明在這種情況下async/await實際執行的操作。 我創建了兩個具有相同邏輯的版本, WaitForItToWorkAsync (使用async/await )和WaitForItToWorkAsyncTap (使用不帶async/await TAP模式 )。 與第二個版本不同,第一個版本非常瑣碎。 因此,盡管async/await在很大程度上是編譯器的語法糖,但它使異步代碼更易於編寫和理解。

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

關於線程的幾句話 這里沒有明確創建其他線程。 在內部, Task.Delay()實現可能使用池線程(我懷疑它們使用Timer Queues ),但是在此特定示例(一個WinForms應用程序)中, await后的繼續將發生在同一UI線程上。 在其他執行環境(例如控制台應用程序)中,它可能會在其他線程上繼續運行。 IMO,Stephen Cleary 撰寫的這篇文章是理解async/await線程概念的必讀文章

如果任務是異步的,則可以嘗試:

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

請參閱http://msdn.microsoft.com/en-us/library/hh195051.aspx

只是提供另一個解決方案

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

您實際上並不需要WaitItForWork方法,只需等待數據庫初始化任務即可:

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

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

如果您有多個調用WaitForItToWork的代碼,則需要將數據庫初始化包裝到Task ,並在所有工作程序中等待它,例如:

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
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM