1 .NET多線程是什么?
1.1 進程與線程
進程是一種正在執行的程序。
線程是程序中的一個執行流。
多線程是指一個程序中可以同時運行多個不同的線程來執行不同的任務。
1.2 .NET中的線程
Thread是創建和控制線程的類。
ManagedThreadId是線程ID。
CurrentThread是獲取當前正在運行的線程。
1.3 同步與異步
同步是調用一旦開始,調用者必須等到方法調用返回后,才能繼續后續的行為。(單線程)
異步調用一旦開始,方法調用就會立即返回,調用者就可以繼續后續的操作。(多線程)
1.4 .NET中的多線程發展
主要有Thread,ThreadPool,Task
Thread就是線程,需要自己調度,直接跟系統對接,相對管理比較復雜及效率差。
ThreadPool是Thread的一個升級版,ThreadPool是從線程池中獲取線程,如果線程池中又空閑的元素,則直接調用,如果沒有才會創建,而Thread則是會一直創建新的線程,要知道開啟一個線程就算什么事都不做也會消耗大約1m的內存,是非常浪費性能的。但是ThreadPool提供的接口比較少。
Task和ThreadPool是一樣的,都是從線程池中取空閑的線程。比ThreadPool調用接口更加豐富。目前.Net使用多線程管理,應該優先使用Task。
代碼:
/// <summary>
/// 多線程發展歷史
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnHistory_Click(object sender, EventArgs e)
{
Console.WriteLine($"Thread start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
var threadStart = new ThreadStart(DoNothing);
var thread = new Thread(threadStart);
thread.Start();
Console.WriteLine($"Thread end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(3000);
Console.WriteLine($"ThreadPool start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
var callback = new WaitCallback(DoNothing);
ThreadPool.QueueUserWorkItem(callback);
Console.WriteLine($"ThreadPool end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(3000);
Console.WriteLine($"Task start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Action action = DoNothing;
Task task = new Task(action);
task.Start();
Console.WriteLine($"Task end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
2 為什么需要多線程?
特點:
- 卡界面:單線程卡,多線程不卡
- 性能好:單線程差,多線程好(資源換性能)
- 執行順序:單線程順序,多線程無序
代碼:
/// <summary>
/// 同步(單線程)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnSync_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnSync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
for (int i = 0; i < 5; i++)
{
DoNothing();
}
Console.WriteLine($"btnSync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
/// <summary>
/// 異步(多線程)
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsync_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnAsync_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
for (int i = 0; i < 5; i++)
{
var ts = new ThreadStart(DoNothing);
var t = new Thread(ts);
t.Start();
}
Console.WriteLine($"btnAsync_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
private void DoNothing()
{
Console.WriteLine($"DoNothing start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
Console.WriteLine($"DoNothing end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
3 如何使用.NET多線程?
3.1 Task
3.1.1 創建任務
1、通過調用任務類構造函數實例化,但通過調用其Start()啟動任務。
Task t1 = new Task(action, "alpha");
t1.Start();
2、通過調用TaskFactory.StartNew(Action < Object>,Object)方法在單個方法調用中實例化和啟動任務。
Task t2 = Task.Factory.StartNew(action, "beta");
3、通過調用Run(Action)方法在單個方法調用中實例化和啟動任務。
Task t3 = Task.Run(action);
3.1.2 從任務中返回值
Result 屬性將阻止調用線程,直到任務完成。
Task<int> task1 = Task<int>.Factory.StartNew(() => 1);
int i = task1.Result;
3.1.3 等待任務完成
可以通過調用 Wait 方法來等待一個或多個任務完成,從而同步調用線程的執行以及它啟動的異步任務。
調用無參數 Wait() 方法以無條件等待,直到任務完成。
調用Wait(Int32)和 Wait(TimeSpan) 方法會阻止調用線程,直到任務完成或超時間隔(以先達到者為准)為止。
調用WaitAny(Task[])方法等待一組任務中第一個任務完成。
調用WaitAll(Task[])方法來等待一系列任務全部完成。
3.1.4 異常處理
調用代碼可以通過使用 try/catch 塊中的以下任意方法來處理異常:
- await task
- task.Wait()
- task.Result
- task.GetAwaiter().GetResult()
代碼:
var task1 = Task.Run(() => { throw new Exception("This exception is expected!"); });
try
{
task1.Wait();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
Console.ReadKey();
3.1.5 取消任務
你可以使用 CancellationTokenSource 類在以后某一時間發出取消請求。
static void Main(string[] args)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var tasks = new ConcurrentBag<Task>();
Task tc;
for (int i = 0; i < 10; i++)
{
var k = i;
tc = Task.Run(() => DoNothing(k, token), token);
tasks.Add(tc);
}
char ch = Console.ReadKey().KeyChar;
if (ch == 'c' || ch == 'C')
{
tokenSource.Cancel();
Console.WriteLine("\n開始取消任務.");
}
try
{
Task.WhenAll(tasks.ToArray());
}
catch (OperationCanceledException)
{
Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
}
finally
{
tokenSource.Dispose();
}
Console.ReadKey();
}
private static void DoNothing(int i, CancellationToken ct)
{
Console.WriteLine($"DoNothing start index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(i * 1000);
if (ct.IsCancellationRequested)
{
Console.WriteLine($"任務已取消 index:{i} id:{Thread.CurrentThread.ManagedThreadId} ");
ct.ThrowIfCancellationRequested();
}
Console.WriteLine($"DoNothing end index:{i} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
3.1.6 實際案例
代碼:
/// <summary>
/// Task實際案例
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnTask_Click(object sender, EventArgs e)
{
Console.WriteLine($"項目組接到任務 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Console.WriteLine($"項目經理設計數據庫,設計原型,分配任務 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
List<Task> tasks = new List<Task>();
tasks.Add(Task.Run(() => Coding("趙XX","前端頁面")));
tasks.Add(Task.Run(() => Coding("王XX", "IOS頁面")));
tasks.Add(Task.Run(() => Coding("黃XX", "后端接口")));
tasks.Add(Task.Run(() => Coding("杜XX", "后端接口")));
TaskFactory taskFactory = new TaskFactory();
taskFactory.ContinueWhenAll(tasks.ToArray(), t =>
{
Console.WriteLine($"項目經理發布,測試人員測試任務 id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
});
}
private void Coding(string personName,string taskName)
{
Console.WriteLine($"{personName}開發{taskName} id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
}
3.2 async和await
Async 和 Await幾乎與創建同步方法一樣創建異步方法。
代碼:
/// <summary>
/// Async和Await應用
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsyncAndAwait_Click(object sender, EventArgs e)
{
Console.WriteLine($"btnAsyncAndAwait_Click start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
DoNothingAsync();
Console.WriteLine($"btnAsyncAndAwait_Click end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
private async Task DoNothingAsync()
{
Console.WriteLine($"DoNothingAsync start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
await Task.Run(() => {
Console.WriteLine($"DoNothingAsync Task start id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
Thread.Sleep(2000);
Console.WriteLine($"DoNothingAsync Task end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
});
Console.WriteLine($"DoNothingAsync end id:{Thread.CurrentThread.ManagedThreadId} time:{DateTime.Now}");
}
