C#基於任務的異步模式開發教程

任務一般是一些比較耗時的操作(IO或者複雜計算),如果在主線程運行,將影響程序的流暢性。所以,我們一般會新建線程處理任務。.NET4開始引進了Task,它對Thread做了大量方便易用的封裝。我們將詳細講述Task的使用方法,以及各種多線程的使用場合。

一、任務設計

1.1 基本設計

一個簡單的耗時操作如下所示:

void Func1()
{
    Thread.Sleep(1000);
}

如果有返回值,將如下所示:

int Func2()
{
    Thread.Sleep(1500);
    return 1;
}

耗時操作如果要新建線程去運行,可以這麼寫:

Task task1 = new Task(Func1);
task1.Start();

Task<int> task2 = new Task<int>(Func2);
task2.Start();

C#5.0引入了async、await這些關鍵字,異步方法的寫法爲:

async Task Func1()
{
    await Task.Delay(1000);
}

async Task<int> Func2()
{
    await Task.Delay(1500);
    return 1;
}

調用時就像同步的方法一樣:

Func1();
Func2();

需要注意的是,雖然代碼看上去是先執行Func1,然後再執行Func2,但實際上,這兩個都是異步方法,它們會同時開始執行。

 

1.2 可取消的任務

在使用Thread時會看到一個Abort方法,用來終止線程。但其實這個方法非常不安全,甚至不能終止,一般不提倡使用。事實上,如果一個耗時方法是我們自己設計的,我們可以想辦法在中途退出;而如果這個方法我們只能調用,無法修改,那這個任務基本無法中止。

假設有一個耗時方法如下所示:

async Task Func1()
{
    await Task.Delay(10000);
}

一旦調用,這個方法將運行10秒。爲了避免這種情況,我們需要對等待進行切割。如:

async Task Func1()
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
    }
}

如果希望中途能夠取消,那麼可以寫成:

async Task Func1(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }
        await Task.Delay(100);
    }
}

調用的方法如下:

CancellationTokenSource cts = new CancellationTokenSource();
await Func1(cts.Token); 

如果需要在另一個線程裏面取消,只需要調用以下語句即可:

cts.Cancel();

 

1.3 進度報告

有時候我們需要實時知道耗時任務的運行進度,例如下載,可能需要向用戶提供一個完成百分比。

帶進度報告功能的異步任務設計如下:

async Task Func1(IProgress<int> progress)
{
    for (int i = 0; i < 100; i++)
    {
        await Task.Delay(100);
        progress.Report(i + 1);
    }
}

調用方法則如下所示:

await Func1(new Progress<int>(p =>
{
    Console.WriteLine(p);
}));

 

二、單任務使用

對於沒有返回值的任務,我們上面已經提到了使用的方法。如果任務有返回值,而我們需要使用這個返回值,調用的方法如下:

(1)使用async、await關鍵字

async Task<int> Func2()
{
    await Task.Delay(1500);
    return 1;
}


//調用方法
int result = await Func2();

(2)不使用async、await關鍵字

int Func2()
{
    Thread.Sleep(1500);
    return 1;
}


//調用方法
Task<int> task = new Task<int>(Func2);
task.ContinueWith(t =>
{
    int result = t.Result;
});

 

三、多任務使用

3.1 所有任務完成後處理

使用WhenAll方法,使用方法如下:

async Task<int> Func1()
{
    await Task.Delay(1000);
    return 1;
}

async Task<int> Func2()
{
    await Task.Delay(1500);
    return 2;
}



Task<int>[] tasks = new Task<int>[] { Func1(), Func2() };
int[] results = await Task.WhenAll(tasks);

3.2 任一任務完成即處理

使用WhenAny方法,使用方法如下:

async Task<int> Func1()
{
    await Task.Delay(1000);
    return 1;
}

async Task<int> Func2()
{
    await Task.Delay(1500);
    return 2;
}



Task<int>[] tasks = new Task<int>[] { Func1(), Func2() };
Task<int> task = await Task.WhenAny(tasks);
int result = task.Result;

WhenAny方法可以有以下的應用場景:

(1)超時判斷。寫一個簡單的延時任務,跟需要執行的任務一同放在列表裏。如果WhenAny先返回延時任務,說明需要執行的任務已經超時了。

(2)一個任務完成,取消執行餘下任務。結合CancellationToken完成這一功能,具體代碼如下:

async Task Func1(CancellationToken cancellationToken)
{
    for (int i = 0; i < 20; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }

        await Task.Delay(100);
    }
}

async Task Func2(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        if (cancellationToken.IsCancellationRequested)
        {
            return;
        }

        await Task.Delay(110);
    }
}




CancellationTokenSource cts = new CancellationTokenSource();
Task[] tasks = new Task[] { Func1(cts.Token), Func2(cts.Token) };
Task task = await Task.WhenAny(tasks);
cts.Cancel();

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章