.NET中異步和多線程的應(yīng)用

今天就跟大家聊聊有關(guān).NET中異步和多線程的應(yīng)用,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

創(chuàng)新互聯(lián)公司長期為上千家客戶提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊從業(yè)經(jīng)驗10年,關(guān)注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務(wù);打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為洪澤企業(yè)提供專業(yè)的網(wǎng)站建設(shè)、成都做網(wǎng)站,洪澤網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。

線程(英文:thread),操作系統(tǒng)技術(shù)中的術(shù)語,是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位,它被包涵在進(jìn)程之中,是行程中的實際運作單位。

Async是異步串行端口,主要應(yīng)用于Modem或Modem池的連接。它主要用于實現(xiàn)遠(yuǎn)程計算機通過公用電話撥入網(wǎng)絡(luò),數(shù)據(jù)速率不高,不要求通信設(shè)備之間保持同步。

一、任務(wù)Task

System.Threading.Tasks在.NET4引入,前面線程的API太多了,控制不方便,而ThreadPool控制能力又太弱,比如做線程的延續(xù)、阻塞、取消、超時等功能不太方便,所以Task就抽象了線程功能,在后臺使用ThreadPool

1、啟動任務(wù)

可以使用TaskFactory類或Task類的構(gòu)造函數(shù)和Start()方法,委托可以提供帶有一個Object類型的輸入?yún)?shù),所以可以給任務(wù)傳遞任意數(shù)據(jù),還漏了一個常用的Task.Run

TaskFactory taskFactory = new TaskFactory();
taskFactory.StartNew(() => 
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
Task.Factory.StartNew(() =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
Task task = new Task(() =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.Start();

只有Task類實例方式需要Start()去啟動任務(wù),當(dāng)然可以RunSynchronously()來同步執(zhí)行任務(wù),主線程會等待,就是用主線程來執(zhí)行這個task任務(wù)

Task task = new Task(() =>
{
    Thread.Sleep(10000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.RunSynchronously();

2、阻塞延續(xù)

在Thread中我們使用join來阻塞等待,在多個Thread時進(jìn)行控制就不太方便。Task中我們使用實例方法Wait阻塞單個任務(wù)或靜態(tài)方法WaitAll和WaitAny阻塞多個任務(wù)

var task = new Task(() =>
{
    Thread.Sleep(5*1000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
var task2 = new Task(() =>
{
    Thread.Sleep(10 * 1000);
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task.Start();
task2.Start();
//task.Wait();//單任務(wù)等待
//Task.WaitAny(task, task2);//任何一個任務(wù)完成就繼續(xù)
Task.WaitAll(task, task2);//任務(wù)都完成才繼續(xù)

如果不希望阻塞主線程,實現(xiàn)當(dāng)一個任務(wù)或幾個任務(wù)完成后執(zhí)行別的任務(wù),可以使用Task靜態(tài)方法WhenAll和WhenAny,他們將返回一個Task,但這個Task不允許你控制,將會在滿足WhenAll和WhenAny里任務(wù)完成時自動完成,然后調(diào)用Task的ContinueWith方法,就可以在一個任務(wù)完成后緊跟開始另一個任務(wù)

Task.WhenAll(task, task2).ContinueWith((t) =>
{
    Console.WriteLine($"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
Task.Factory工廠中也存在類似ContinueWhenAll和ContinueWhenAny

3、任務(wù)層次結(jié)構(gòu)

不僅可以在一個任務(wù)結(jié)束后執(zhí)行另一個任務(wù),也可以在一個任務(wù)內(nèi)啟動一個任務(wù),這就啟動了一個父子層次結(jié)構(gòu)

var parentTask = new Task(()=> 
{
    Console.WriteLine($"parentId={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    Thread.Sleep(5*1000);
    var childTask = new Task(() =>
    {
        Thread.Sleep(10 * 1000);
        Console.WriteLine($"childId={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}")
    });
    childTask.Start();
});
parentTask.Start();

如果父任務(wù)在子任務(wù)之前結(jié)束,父任務(wù)的狀態(tài)為WaitingForChildrenToComplete,當(dāng)子任務(wù)也完成時,父任務(wù)的狀態(tài)就變?yōu)镽anToCompletion,當(dāng)然,在創(chuàng)建任務(wù)時指定TaskCreationOptions枚舉參數(shù),可以控制任務(wù)的創(chuàng)建和執(zhí)行的可選行為

4、枚舉參數(shù)

簡單介紹下創(chuàng)建任務(wù)中的TaskCreationOptions枚舉參數(shù),創(chuàng)建任務(wù)時我們可以提供TaskCreationOptions枚舉參數(shù),用于控制任務(wù)的創(chuàng)建和執(zhí)行的可選行為的標(biāo)志

AttachedToParent:指定將任務(wù)附加到任務(wù)層次結(jié)構(gòu)中的某個父級,意思就是建立父子關(guān)系,父任務(wù)必須等待子任務(wù)完成才可以繼續(xù)執(zhí)行。和WaitAll效果一樣。上面例子如果在創(chuàng)建子任務(wù)時指定TaskCreationOptions.AttachedToParent,那么父任務(wù)wait時也會等子任務(wù)的結(jié)束

DenyChildAttach:不讓子任務(wù)附加到父任務(wù)上

LongRunning:指定是長時間運行任務(wù),如果事先知道這個任務(wù)會耗時比較長,建議設(shè)置此項。這樣,Task調(diào)度器會創(chuàng)建Thread線程,而不使用ThreadPool線程。因為你長時間占用ThreadPool線程不還,那它可能必要時會在線程池中開啟新的線程,造成調(diào)度壓力

PreferFairness:盡可能公平的安排任務(wù),這意味著較早安排的任務(wù)將更可能較早運行,而較晚安排運行的任務(wù)將更可能較晚運行。實際通過把任務(wù)放到線程池的全局隊列中,讓工作線程去爭搶,默認(rèn)是在本地隊列中。

另一個枚舉參數(shù)是ContinueWith方法中的TaskContinuationOptions枚舉參數(shù),它除了擁有幾個和上面同樣功能的枚舉值外,還擁有控制任務(wù)的取消延續(xù)等功能

LazyCancellation:在延續(xù)取消的情況下,防止延續(xù)的完成直到完成先前的任務(wù)。什么意思呢?

CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
var task1 = new Task(() => 
{
    Console.WriteLine($"task1 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
var task2 = task1.ContinueWith(t =>
{
    Console.WriteLine($"task2 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
},source.Token);
var task3 = task2.ContinueWith(t =>
{
    Console.WriteLine($"task3 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
});
task1.Start();

上面例子我們企圖task1->task2->task3順序執(zhí)行,然后通過CancellationToken來取消task2的執(zhí)行。結(jié)果會是怎樣呢?結(jié)果task1和task3會并行執(zhí)行(task3也是會執(zhí)行的,而且是和task1并行,等于原來的一條鏈變成了兩條鏈),然后我們嘗試使用

LazyCancellation,
var task2 = task1.ContinueWith(t =>
{
    Console.WriteLine($"task2 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
},source.Token,TaskContinuationOptions.LazyCancellation,TaskScheduler.Current);

這樣,將會在task1執(zhí)行完成后,task2才去判斷source.Token,為Cancel就不執(zhí)行,接下來執(zhí)行task3就保證了原來的順序

ExecuteSynchronously:指定應(yīng)同步執(zhí)行延續(xù)任務(wù),比如上例中,在延續(xù)任務(wù)task2中指定此參數(shù),則task2會使用執(zhí)行task1的線程來執(zhí)行,這樣防止線程切換,可以做些共有資源的訪問。不指定的話就隨機,但也能也用到task1的線程

NotOnRanToCompletion:延續(xù)任務(wù)必須在前面任務(wù)非完成狀態(tài)下執(zhí)行

OnlyOnRanToCompletion:延續(xù)任務(wù)必須在前面任務(wù)完成狀態(tài)才能執(zhí)行

NotOnFaulted,OnlyOnCanceled,OnlyOnFaulted等等

5、任務(wù)取消

在上篇使用Thread時,我們使用一個變量isStop標(biāo)記是否取消任務(wù),這種訪問共享變量的方式難免會出問題。task中提出CancellationTokenSource類專門處理任務(wù)取消,常見用法看下面代碼注釋

CancellationTokenSource source = new CancellationTokenSource();//構(gòu)造函數(shù)中也可指定延遲取消
//注冊一個取消時調(diào)用的委托
source.Token.Register(() =>
{
    Console.WriteLine("當(dāng)前source已經(jīng)取消,可以在這里做一些其他事情(比如資源清理)...");
});
var task1 = new Task(() => 
{
    while (!source.IsCancellationRequested)
    {
        Console.WriteLine($"task1 id={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}");
    }
},source.Token);
task1.Start();
//source.Cancel();//取消
source.CancelAfter(1000);//延時取消

6、任務(wù)結(jié)果

讓子線程返回結(jié)果,可以將信息寫入到線程安全的共享變量中去,或則使用可以返回結(jié)果的任務(wù)。使用Task的泛型版本Task<TResult>,就可以定義返回結(jié)果的任務(wù)。Task是繼承自Task的,Result獲取結(jié)果時是要阻塞等待直到任務(wù)完成返回結(jié)果的,內(nèi)部判斷沒有完成則wait。通過TaskStatus屬性可獲得此任務(wù)的狀態(tài)是啟動、運行、異常還是取消等

var task = new Task<string>(() =>
{
     return "hello ketty";
});
task.Start();
string result = task.Result;

7、異常

可以使用AggregateException來接受任務(wù)中的異常信息,這是一個聚合異常繼承自Exception,可以遍歷獲取包含的所有異常,以及進(jìn)行異常處理,決定是否繼續(xù)往上拋異常等

var task = Task.Factory.StartNew(() =>
{
    var childTask1 = Task.Factory.StartNew(() =>
    {
        throw new Exception("childTask1異常...");
    },TaskCreationOptions.AttachedToParent);
    var childTask12= Task.Factory.StartNew(() =>
    {
        throw new Exception("childTask2異常...");
    }, TaskCreationOptions.AttachedToParent);
});
try
{
    try
    {
        task.Wait();
    }
    catch (AggregateException ex)
    {
        foreach (var item in ex.InnerExceptions)
        {
            Console.WriteLine($"message{item.InnerException.Message}");
        }
        ex.Handle(x =>
        {
            if (x.InnerException.Message == "childTask1異常...")
            {
                return true;//異常被處理,不繼續(xù)往上拋了
            }
            return false;
        });
    }
}
catch (Exception ex)
{
    throw;
}

二、并行Parallel

1、Parallel.For()、Parallel.ForEach()

在.NET4中,另一個新增的抽象的線程時Parallel類。這個類定義了并行的for和foreach的靜態(tài)方法。Parallel.For()和Parallel.ForEach()方法多次調(diào)用一個方法,而Parallel.Invoke()方法允許同時調(diào)用不同的方法。首先Parallel是會阻塞主線程的,它將讓主線程也參與到任務(wù)中

Parallel.For()類似于for允許語句,并行迭代同一個方法,迭代順序沒有保證的

ParallelLoopResult result = Parallel.For(0, 10, i =>
{
    Console.WriteLine($"{i} task:{Task.CurrentId} thread:{Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine(result.IsCompleted);

也可以提前中斷Parallel.For()方法。For()方法的一個重載版本接受Action<int,parallelloopstate style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;">類型參數(shù)。一般不使用,像下面這樣,本想大于5就停止,但實際也可能有大于5的任務(wù)已經(jīng)在跑了??梢酝ㄟ^ParallelOptions傳入允許最大線程數(shù)以及取消Token等

ParallelLoopResult result = Parallel.For(0, 10, new ParallelOptions() { MaxDegreeOfParallelism = 8 },(i,loop) =>
{
    Console.WriteLine($"{i} task:{Task.CurrentId} thread:{Thread.CurrentThread.ManagedThreadId}");
    if (i > 5)
    {
        loop.Break();
    }
});

2、Parallel.For<TLocal>

For還有一個高級泛型版本,相當(dāng)于并行的聚合計算

ParallelLoopResult For<TLocal>(int fromInclusive, int toExclusive, Func<TLocal> localInit, Func<int, ParallelLoopState, TLocal, TLocal> body, Action<TLocal> localFinally);

像下面這樣我們求0…100的和,第三個參數(shù)更定一個種子初始值,第四個參數(shù)迭代累計,最后聚合

int totalNum = 0;
Parallel.For<int>(0, 100, () => { return 0; }, (current, loop, total) =>
{
    total += current;
    return total;
}, (total) =>
{
    Interlocked.Add(ref totalNum, total);
});

上面For用來處理數(shù)組數(shù)據(jù),F(xiàn)orEach()方法用來處理非數(shù)組的數(shù)據(jù)任務(wù),比如字典數(shù)據(jù)繼承自IEnumerable的集合等

3、Parallel.Invoke()

Parallel.Invoke()則可以并行調(diào)用不同的方法,參數(shù)傳遞一個Action的委托數(shù)組

Parallel.Invoke(() => { Console.WriteLine($"方法1 thread:{Thread.CurrentThread.ManagedThreadId}"); }
    , () => { Console.WriteLine($"方法2 thread:{Thread.CurrentThread.ManagedThreadId}"); }
    , () => { Console.WriteLine($"方法3 thread:{Thread.CurrentThread.ManagedThreadId}"); });

4、PLinq

Plinq,為了能夠達(dá)到最大的靈活度,linq有了并行版本。使用也很簡單,只需要將原始集合AsParallel就轉(zhuǎn)換為支持并行化的查詢。也可以AsOrdered來順序執(zhí)行,取消Token,強制并行等

var nums = Enumerable.Range(0, 100);
var query = from n in nums.AsParallel()
            select new
            {
                thread=$"tid={Thread.CurrentThread.ManagedThreadId},datetime={DateTime.Now}"
            };

三、異步等待AsyncAwait

異步編程模型,可能還需要大篇幅來學(xué)習(xí),這里先介紹下基本用法,內(nèi)在本質(zhì)需要用ILSpy反編譯來看,以后可能要分專題總結(jié)。文末先給幾個參考資料,有興趣自己闊以先琢磨琢磨鴨

1、簡單使用

這是.NET4.5開始提供的一對語法糖,使得可以較簡便的使用異步編程。async用在方法定義前面,await只能寫在帶有async標(biāo)記的方法中,任何方法都可以增加async,一般成對出現(xiàn),只有async沒有意義,只有await會報錯,請先看下面的示例

private static async void AsyncTest()
{
    //主線程執(zhí)行
    Console.WriteLine($"before await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    TaskFactory taskFactory = new TaskFactory();
    Task task = taskFactory.StartNew(() =>
    {
        Thread.Sleep(3000);
        Console.WriteLine($"task ThreadId={Thread.CurrentThread.ManagedThreadId}");
    });
    await task;//主線程到這里就返回了,執(zhí)行主線程任務(wù)
    //子線程執(zhí)行,其實是封裝成委托,在task之后成為回調(diào)(編譯器功能  狀態(tài)機實現(xiàn)) 后面相當(dāng)于task.ContinueWith()
    //這個回調(diào)的線程是不確定的:可能是主線程  可能是子線程  也可能是其他線程,在winform中是主線程
    Console.WriteLine($"after await ThreadId={Thread.CurrentThread.ManagedThreadId}");
}

.NET中異步和多線程的應(yīng)用

一般使用async都會讓方法返回一個Task的,像下面這樣復(fù)雜一點的

private static async Task<string> AsyncTest2()
{
    Console.WriteLine($"before await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    TaskFactory taskFactory = new TaskFactory();
    string x = await taskFactory.StartNew(() =>
      {
          Thread.Sleep(3000);
          Console.WriteLine($"task ThreadId={Thread.CurrentThread.ManagedThreadId}");
          return "task over";
      });
    Console.WriteLine($"after await ThreadId={Thread.CurrentThread.ManagedThreadId}");
    return x;
}

通過var reslult = AsyncTest2().Result;調(diào)用即可。但注意如果調(diào)用Wait或Result的代碼位于UI線程,Task的實際執(zhí)行在其他線程,其需要返回UI線程則會造成死鎖,所以應(yīng)該Async all the way

2、優(yōu)雅

從上面簡單示例中可以看出異步編程的執(zhí)行邏輯:主線程A邏輯->異步任務(wù)線程B邏輯->主線程C邏輯。

異步方法的返回類型只能是void、Task、Task。示例中異步方法的返回值類型是Task,通常void也不推薦使用,沒有返回值直接用Task就是

上一篇也大概了解到如果我們要在任務(wù)中更新UI,需要調(diào)用Invoke通知UI線程來更新,代碼看起來像下面這樣,在一個任務(wù)后去更新UI

private void button1_Click(object sender, EventArgs e)
{
    var ResultTask = Task.Run(() => {
        Thread.Sleep(5000);
        return "任務(wù)完成";
    });
    ResultTask.ContinueWith((r)=> 
    {
        textBox1.Invoke(() => {
            textBox1.Text = r.Result;
        });
    });
}

如果使用async/await會看起來像這樣,是不是優(yōu)雅了許多。以看似同步編程的方式實現(xiàn)異步

private async void button1_Click(object sender, EventArgs e)
{
    var t = Task.Run(() => {
        Thread.Sleep(5000);
        return "任務(wù)完成";
    });
    textBox1.Text = await t;
}

3、最后

在.NET 4.5中引入的Async和Await兩個新的關(guān)鍵字后,用戶能以一種簡潔直觀的方式實現(xiàn)異步編程。甚至都不需要改變代碼的邏輯結(jié)構(gòu),就能將原來的同步函數(shù)改造為異步函數(shù)。

在內(nèi)部實現(xiàn)上,Async和Await這兩個關(guān)鍵字由編譯器轉(zhuǎn)換為狀態(tài)機,通過System.Threading.Tasks中的并行類實現(xiàn)代碼的異步執(zhí)行。

看完上述內(nèi)容,你們對.NET中異步和多線程有進(jìn)一步的了解嗎?如果還想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀。

網(wǎng)站名稱:.NET中異步和多線程的應(yīng)用
URL地址:http://muchs.cn/article28/ihggcp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供Google、品牌網(wǎng)站建設(shè)、面包屑導(dǎo)航、網(wǎng)站建設(shè)、品牌網(wǎng)站制作定制網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站