異步編程(或多線程編程)

1 .NET多線程是什麼?

1.1 進程與線程

進程是一種正在執行的程序。前端

線程是程序中的一個執行流。數據庫

多線程是指一個程序中能夠同時運行多個不一樣的線程來執行不一樣的任務。編程

1.2 .NET中的線程

Thread是建立和控制線程的類。後端

ManagedThreadId是線程ID。多線程

CurrentThread是獲取當前正在運行的線程。異步

1.3 同步與異步

同步是調用一旦開始,調用者必須等到方法調用返回後,才能繼續後續的行爲。(單線程)async

異步調用一旦開始,方法調用就會當即返回,調用者就能夠繼續後續的操做。(多線程)ide

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 建立任務

一、經過調用任務類構造函數實例化,但經過調用其Start()啓動任務。

Task t1 = new Task(action, "alpha");
t1.Start();

二、經過調用TaskFactory.StartNew(Action < Object>,Object)方法在單個方法調用中實例化和啓動任務。

Task t2 = Task.Factory.StartNew(action, "beta");

三、經過調用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}");
}

4 參考

相關文章
相關標籤/搜索