.NET4.0開始引入Task
,它的出現大大簡化了異步編程的複雜度,相較於傳統的Thread
和ThreadPool
,Task
更加容易控制和使用,下面就來看看它的具體用法。
1、一個簡單的串行程序
串行程序大家肯定不陌生,說白了就是從上到下按順序執行,看下面一段代碼:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
MethodA();
MethodB();
Console.WriteLine("Hello World");
Console.ReadKey();
}
static void MethodA()
{
Thread.Sleep(4000);
Console.WriteLine("This is MethodA");
}
static void MethodB()
{
Thread.Sleep(2000);
Console.WriteLine("This is MethodB");
}
}
}
運行結果如下所示:
This is MethodA
This is MethodB
Hello World
這段代碼很簡單,首先執行MethodA
,然後執行MethodB
,最後輸出Hello World
,想必大家對運行結果應該沒什麼疑問。但我們也需要考慮一個問題:在串行程序中,如果每個方法都耗時很長時間,一定會讓主程序處於卡死狀態,那麼能否讓這些耗時的方法同時執行,使其不影響之後的程序運行呢?
。答案當然是可以的,這個時候我們就需要使用Task
來解決問題,下面就來看看Task
是怎麼解決這個問題的。
2、一個簡單的Task程序
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Task a = new Task(MethodA);
Task b = new Task(MethodB);
a.Start();
b.Start();
Console.WriteLine("Hello World");
Console.ReadKey();
}
static void MethodA()
{
Thread.Sleep(4000);
Console.WriteLine("This is MethodA");
}
static void MethodB()
{
Thread.Sleep(2000);
Console.WriteLine("This is MethodB");
}
}
}
運行結果如下所示:
Hello World
This is MethodB
This is MethodA
是不是感覺很驚喜?從運行結果來看,Task
並沒有阻礙之後程序的運行,如果遇到一個耗時的方法,那就把它放進Task
吧。
3、Task的幾種創建方式
創建Task
的方式很多,主要就是new Task()
、Task.Factory.StartNew()
、Task.Run()
這三種方式,下面的代碼演示瞭如何創建Task
:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 第一種創建方式
Task a = new Task(Method);
a.Start();
// 第二種創建方式
Task b = new Task(() =>
{
Thread.Sleep(3000);
Console.WriteLine("This is B");
});
b.Start();
// 第三種創建方式
Task c = Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
Console.WriteLine("This is C");
});
// 第四種創建方式
Task d = Task.Run(() =>
{
Thread.Sleep(1000);
Console.WriteLine("This is D");
});
Console.WriteLine("Hello World");
Console.ReadKey();
}
static void Method()
{
Thread.Sleep(4000);
Console.WriteLine("This is A");
}
}
}
運行結果如下所示:
Hello World
This is D
This is C
This is B
This is A
4、創建帶返回值的Task
在前面的demo裏,我們創建的Task
都沒有返回值,但有些時候我們也需要創建一些帶返回值的Task
,下面的代碼演示瞭如何創建帶返回值的Task
:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 第一種創建方式
Task<string> a = new Task<string>(() =>
{
Thread.Sleep(4000);
return "This is A";
});
a.Start();
Console.WriteLine(a.Result);
// 第二種創建方式
Task<double> b = Task<double>.Factory.StartNew(() =>
{
Thread.Sleep(3000);
return 1;
});
Console.WriteLine(b.Result);
// 第三種創建方式
Task<bool> c = Task<bool>.Run(() =>
{
return DateTime.Now.Year == 2020;
});
Console.WriteLine(c.Result);
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
我們一般使用Result
屬性獲取一個Task
執行後的結果,運行結果如下所示:
This is A
1
True
Hello World
這裏你可能會發現一個問題,我們使用的不是Task
嗎?爲什麼這段代碼是按照順序執行的?這好像跟串行程序沒什麼區別啊!這是因爲:task.Result取值的時候,後臺線程還沒執行完,則會等待其執行完畢!
,想讓帶返回值的Task
像不帶返回值的Task
那樣執行,就需要使用async
、await
這兩個關鍵字,後面會進行說明。
5、等待所有任務完成——Task.WaitAll
有時候我們希望等所有的Task
執行完畢後再執行之後的代碼,這就需要用到Task.WaitAll
方法,看下面一段代碼:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 任務a
Task a = Task.Run(() =>
{
Thread.Sleep(4000);
Console.WriteLine("This is A");
});
// 任務b
Task b = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("This is B");
});
// 等待兩個任務完成
Task.WaitAll(a, b);
Console.WriteLine("等待全部任務完成纔會輸出");
Console.ReadKey();
}
}
}
運行結果如下所示:
This is B
This is A
等待全部任務完成纔會輸出
有時候我們也會給Task.WaitAll
方法規定一個時間期限,如果超出了這個時間期限則執行其他代碼,在下面這段代碼中,規定Task.WaitAll
方法的時間期限爲3秒,如果超出了3秒,則輸出Hello World
:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 任務a
Task a = Task.Run(() =>
{
Thread.Sleep(5000);
Console.WriteLine("This is A");
});
// 任務b
Task b = Task.Run(() =>
{
Thread.Sleep(4000);
Console.WriteLine("This is B");
});
// 等待兩個任務完成,如果3秒內未完成則輸出Hello World
bool finish = Task.WaitAll(new Task[] { a, b }, 3000);
if (!finish)
{
Console.WriteLine("Hello World");
}
Console.ReadKey();
}
}
}
運行結果如下所示:
Hello World
This is B
This is A
6、等待其中一個任務完成——Task.WaitAny
與Task.WaitAll
方法不同,Task.WaitAny
方法不需要等待所有任務完成,只要其中的一個任務完成就會繼續執行之後的代碼,下面這段代碼演示了Task.WaitAny
的使用方法:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 任務a
Task a = Task.Run(() =>
{
Thread.Sleep(4000);
Console.WriteLine("This is A");
});
// 任務b
Task b = Task.Run(() =>
{
Thread.Sleep(2000);
Console.WriteLine("This is B");
});
// 等待其中一個任務完成
Task.WaitAny(a, b);
Console.WriteLine("等待其中一個任務完成就會輸出");
Console.ReadKey();
}
}
}
運行結果如下所示:
This is B
等待其中一個任務完成就會輸出
This is A
7、按順序執行Task
有時候我們希望創建的任務按照一定的順序執行,比如A任務結束後才執行B任務,B任務結束後再執行C任務,這就需要用到ContinueWith
方法,看下面一段代碼:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// 連續任務
Task task = Task.Run(() =>
{
Thread.Sleep(4000);
Console.WriteLine("This is A");
})
.ContinueWith(t =>
{
Thread.Sleep(3000);
Console.WriteLine("This is B");
})
.ContinueWith(t =>
{
Thread.Sleep(2000);
Console.WriteLine("This is C");
})
.ContinueWith(t =>
{
Thread.Sleep(1000);
Console.WriteLine("This is D");
});
Console.WriteLine("Hello World");
Console.ReadKey();
}
}
}
運行結果如下所示:
Hello World
This is A
This is B
This is C
This is D
8、async與await
.NET4.5開始引入了async
、await
這兩個關鍵字,這兩個關鍵字的出現大大簡化了異步編程的難度,使用這兩個關鍵字的時候需要注意:async和await成對出現纔有意義
、async只能修飾void、Task、Task<T>這三種返回類型的方法
、await只能修飾Task和Task<T>
。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Print();
Console.WriteLine("Hello World");
Console.ReadKey();
}
static async void Print()
{
string a = await GetMessageOneAsync();
string b = await GetMessageTwoAsync();
Console.WriteLine(a);
Console.WriteLine(b);
}
static async Task<string> GetMessageOneAsync()
{
return await Task<string>.Run(() =>
{
Thread.Sleep(5000);
return "This is A";
});
}
static async Task<string> GetMessageTwoAsync()
{
return await Task<string>.Run(() =>
{
Thread.Sleep(3000);
return "This is B";
});
}
}
}
運行結果如下所示:
Hello World
This is A
This is B
9、一個WinForm中使用async、await的實例
我在這裏做了一個WinForm的demo,界面如下圖所示:
代碼如下所示:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 按鈕一
private void button1_Click(object sender, EventArgs e)
{
int a = GetParameterOne();
int b = GetParameterTwo();
textBox1.Text = (a + b).ToString();
}
// 按鈕二
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("主線程提示框", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
// 假設獲取第一個參數花費了5秒
private int GetParameterOne()
{
Thread.Sleep(5000);
return 20;
}
// 假設獲取第二個參數花費了3秒
private int GetParameterTwo()
{
Thread.Sleep(3000);
return 50;
}
}
}
運行之後你會發現:點擊按鈕一,整個界面處於假死狀態,窗體無法拖動,按鈕二無法點擊
,對於用戶來說這顯然是無法接受的,因此我們希望的效果是:點擊按鈕一,窗體仍舊可以拖動,按鈕二不受干擾,點擊後能彈出提示框
。下面我們就來試試用async
和await
來解決,代碼如下:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
// 按鈕一
private async void button1_Click(object sender, EventArgs e)
{
int a = await GetParameterOneAsync();
int b = await GetParameterTwoAsync();
textBox1.Text = (a + b).ToString();
}
// 按鈕二
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show("主線程不受干擾", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
// 假設獲取第一個參數花費了5秒
private async Task<int> GetParameterOneAsync()
{
return await Task<int>.Run(() =>
{
Thread.Sleep(5000);
return 20;
});
}
// 假設獲取第二個參數花費了3秒
private async Task<int> GetParameterTwoAsync()
{
return await Task<int>.Run(() =>
{
Thread.Sleep(3000);
return 50;
});
}
}
}
運行之後你會發現:點擊按鈕一,窗體可以拖動,按鈕二可以點擊,點擊後能彈出對話框
,因此這種情況下我們可以考慮使用async
和await
來提高用戶體驗。