程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!

C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!

編輯:C#入門知識

C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!。本站提示廣大學習愛好者:(C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!)文章只能為提供參考,不一定能成為您想要的結果。以下是C#中 Thread,Task,Async/Await,IAsyncResult 的那些事兒!正文


說起異步,Thread,Task,async/await,IAsyncResult 這些東西一定是繞不開的,明天就來順次聊聊他們

1.線程(Thread)

多線程的意義在於一個使用順序中,有多個執行局部可以同時執行;關於比擬耗時的操作(例如io,數據庫操作),或許等候呼應(如WCF通訊)的操作,可以獨自開啟後台線程來執行,這樣主線程就不會阻塞,可以持續往下執行;等到後台線程執行終了,再告訴主線程,然後做出對應操作!

在C#中開啟新線程比擬復雜

static void Main(string[] args)
{
    Console.WriteLine("主線程開端");
    //IsBackground=true,將其設置為後台線程
    Thread t = new Thread(Run) { IsBackground = true };
    t.Start();
   Console.WriteLine("主線程在做其他的事!"); //主線程完畢,後台線程會自動完畢,不論有沒有執行完成 //Thread.Sleep(300); Thread.Sleep(1500); Console.WriteLine("主線程完畢"); } static void Run() { Thread.Sleep(700); Console.WriteLine("這是後台線程調用"); }

 執行後果如下圖,

可以看到在啟動後台線程之後,主線程持續往下執行了,並沒有等到後台線程執行完之後。

1.1 線程池

試想一下,假如有少量的義務需求處置,例如網站後台關於HTTP懇求的處置,那是不是要對每一個懇求創立一個後台線程呢?顯然不適宜,這會占用少量內存,而且頻繁地創立的進程也會嚴重影響速度,那怎樣辦呢?線程池就是為理解決這一問題,把創立的線程存起來,構成一個線程池(外面有多個線程),當要處置義務時,若線程池中有閒暇線程(前一個義務執行完成後,線程不會被回收,會被設置為閒暇形態),則直接調用線程池中的線程執行(例asp.net處置機制中的Application對象),

運用事例:

for (int i = 0; i < 10; i++)
{
    ThreadPool.QueueUserWorkItem(m =>
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
    });
}
Console.Read();

運轉後果:

可以看到,雖然執行了10次,但並沒有創立10個線程。

 1.2 信號量(Semaphore)

 Semaphore擔任協調線程,可以限制對某一資源訪問的線程數量

 這裡對SemaphoreSlim類的用法做一個復雜的事例:

static SemaphoreSlim semLim = new SemaphoreSlim(3); //3表示最多只能有三個線程同時訪問
static void Main(string[] args)
{
    for (int i = 0; i < 10; i++)
    {
        new Thread(SemaphoreTest).Start();
    }
    Console.Read();
}
static void SemaphoreTest()
{
    semLim.Wait();
    Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "開端執行");
    Thread.Sleep(2000);
    Console.WriteLine("線程" + Thread.CurrentThread.ManagedThreadId.ToString() + "執行終了");
    semLim.Release();
}

執行後果如下:

可以看到,剛開端只要三個線程在執行,當一個線程執行終了並釋放之後,才會有新的線程來執行辦法!

除了SemaphoreSlim類,還可以運用Semaphore類,覺得愈加靈敏,感興味的話可以搜一下,這裡就不做演示了!

2.Task

Task是.NET4.0參加的,跟線程池ThreadPool的功用相似,用Task開啟新義務時,會從線程池中調用線程,而Thread每次實例化都會創立一個新的線程。

Console.WriteLine("主線程啟動");
//Task.Run啟動一個線程
//Task啟動的是後台線程,要在主線程中等候後台線程執行終了,可以調用Wait辦法
//Task task = Task.Factory.StartNew(() => { Thread.Sleep(1500); Console.WriteLine("task啟動"); });
Task task = Task.Run(() => { 
    Thread.Sleep(1500);
    Console.WriteLine("task啟動");
});
Thread.Sleep(300);
task.Wait();
Console.WriteLine("主線程完畢");

執行後果如下:

開啟新義務的辦法:Task.Run()或許Task.Factory.StartNew(),開啟的是後台線程

要在主線程中等候後台線程執行終了,可以運用Wait辦法(會以同步的方式來執行)。不必Wait則會以異步的方式來執行。

比擬一下Task和Thread:

static void Main(string[] args)
{
    for (int i = 0; i < 5; i++)
    {
        new Thread(Run1).Start();
    }
    for (int i = 0; i < 5; i++)
    {
        Task.Run(() => { Run2(); });
    }
}
static void Run1()
{
    Console.WriteLine("Thread Id =" + Thread.CurrentThread.ManagedThreadId);
}
static void Run2()
{
    Console.WriteLine("Task調用的Thread Id =" + Thread.CurrentThread.ManagedThreadId);
}

執行後果:

可以看出來,直接用Thread會開啟5個線程,用Task(用了線程池)開啟了3個!

2.1 Task<TResult>

Task<TResult>就是有前往值的Task,TResult就是前往值類型。

Console.WriteLine("主線程開端");
//前往值類型為string
Task<string> task = Task<string>.Run(() => {
    Thread.Sleep(2000); 
    return Thread.CurrentThread.ManagedThreadId.ToString(); 
});
//會等到task執行終了才會輸入;
Console.WriteLine(task.Result);
Console.WriteLine("主線程完畢");

運轉後果:

經過task.Result可以取到前往值,若取值的時分,後台線程還沒執行完,則會等候其執行終了!

復雜提一下:

Task義務可以經過CancellationTokenSource類來取消,覺得用得不多,用法比擬復雜,感興味的話可以搜一下!

 3. async/await

async/await是C#5.0中推出的,先上用法:

static void Main(string[] args)
{
    Console.WriteLine("-------主線程啟動-------");
    Task<int> task = GetStrLengthAsync();
    Console.WriteLine("主線程持續執行");
    Console.WriteLine("Task前往的值" + task.Result);
    Console.WriteLine("-------主線程完畢-------");
}

static async Task<int> GetStrLengthAsync()
{
    Console.WriteLine("GetStrLengthAsync辦法開端執行");
    //此處前往的<string>中的字符串類型,而不是Task<string>
    string str = await GetString();
    Console.WriteLine("GetStrLengthAsync辦法執行完畢");
    return str.Length;
}

static Task<string> GetString()
{
   //Console.WriteLine("GetString辦法開端執行") return Task<string>.Run(() => { Thread.Sleep(2000); return "GetString的前往值"; }); }

async用來修飾辦法,標明這個辦法是異步的,聲明的辦法的前往類型必需為:void,Task或Task<TResult>。

await必需用來修飾Task或Task<TResult>,而且只能呈現在曾經用async關鍵字修飾的異步辦法中。通常狀況下,async/await成對呈現才有意義,

看看運轉後果:

可以看出來,main函數調用GetStrLengthAsync辦法後,在await之前,都是同步執行的,直到遇到await關鍵字,main函數才前往持續執行。

那麼能否是在遇到await關鍵字的時分順序自動開啟了一個後台線程去執行GetString辦法呢?

如今把GetString辦法中的那行正文加上,運轉的後果是:

大家可以看到,在遇到await關鍵字後,沒有持續執行GetStrLengthAsync辦法前面的操作,也沒有馬上反回到main函數中,而是執行了GetString的第一行,以此可以判別await這裡並沒有開啟新的線程去執行GetString辦法,而是以同步的方式讓GetString辦法執行,等到執行到GetString辦法中的Task<string>.Run()的時分才由Task開啟了後台線程!

那麼await的作用是什麼呢?

可以從字面上了解,下面提到task.wait可以讓主線程等候後台線程執行終了,await和wait相似,異樣是等候,等候Task<string>.Run()開端的後台線程執行終了,不同的是await不會阻塞主線程,只會讓GetStrLengthAsync辦法暫停執行。

那麼await是怎樣做到的呢?有沒有開啟新線程去等候?

只要兩個線程(主線程和Task開啟的線程)!至於怎樣做到的(我也不知道......>_<),大家有興味的話研討下吧!

4.IAsyncResult

IAsyncResult自.NET1.1起就有了,包括可異步操作的辦法的類需求完成它,Task類就完成了該接口

在不借助於Task的狀況下怎樣完成異步呢?

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("主順序開端--------------------");
        int threadId;
        AsyncDemo ad = new AsyncDemo();
        AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

        IAsyncResult result = caller.BeginInvoke(3000,out threadId, null, null);
        Thread.Sleep(0);
        Console.WriteLine("主線程線程 {0} 正在運轉.",Thread.CurrentThread.ManagedThreadId)
        //會阻塞線程,直到後台線程執行終了之後,才會往下執行
        result.AsyncWaitHandle.WaitOne();
        Console.WriteLine("主順序在做一些事情!!!");
        //獲取異步執行的後果
        string returnValue = caller.EndInvoke(out threadId, result);
        //釋放資源
        result.AsyncWaitHandle.Close();
        Console.WriteLine("主順序完畢--------------------");
        Console.Read();
    }
}
public class AsyncDemo
{
    //供後台線程執行的辦法
    public string TestMethod(int callDuration, out int threadId)
    {
        Console.WriteLine("測試辦法開端執行.");
        Thread.Sleep(callDuration);
        threadId = Thread.CurrentThread.ManagedThreadId;
        return String.Format("測試辦法執行的時間 {0}.", callDuration.ToString());
    }
}
public delegate string AsyncMethodCaller(int callDuration, out int threadId);

關鍵步驟就是白色字體的局部,運轉後果:

和Task的用法差別不是很大!result.AsyncWaitHandle.WaitOne()就相似Task的Wait。

 5.Parallel

最後說一下在循環中開啟多線程的復雜辦法:

Stopwatch watch1 = new Stopwatch();
watch1.Start();
for (int i = 1; i <= 10; i++)
{
    Console.Write(i + ",");
    Thread.Sleep(1000);
}
watch1.Stop();
Console.WriteLine(watch1.Elapsed);

Stopwatch watch2 = new Stopwatch();
watch2.Start();

//會調用線程池中的線程
Parallel.For(1, 11, i =>
{
    Console.WriteLine(i + ",線程ID:" + Thread.CurrentThread.ManagedThreadId);
    Thread.Sleep(1000);
});
watch2.Stop();
Console.WriteLine(watch2.Elapsed);

運轉後果:

循環List<T>:

List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6, 6, 7, 8, 9 };
Parallel.ForEach<int>(list, n => { Console.WriteLine(n); Thread.Sleep(1000); });

執行Action[]數組外面的辦法:

Action[] actions = new Action[] { 
   new Action(()=>{
       Console.WriteLine("辦法1");
   }),
    new Action(()=>{
       Console.WriteLine("辦法2");
   })
};
Parallel.Invoke(actions);
6.異步的回調

為了簡約(偷懶),文中一切Task<TResult>的前往值都是直接用task.result獲取,這樣假如後台義務沒有執行終了的話,主線程會等候其執行終了。這樣的話就和同步一樣了,普通狀況下不會這麼用。復雜演示一下Task回調函數的運用:

Console.WriteLine("主線程開端");
Task<string> task = Task<string>.Run(() => {
    Thread.Sleep(2000); 
    return Thread.CurrentThread.ManagedThreadId.ToString(); 
});
//會等就任務執行完之後執行
task.GetAwaiter().OnCompleted(() =>
{
    Console.WriteLine(task.Result);
});
Console.WriteLine("主線程完畢");
Console.Read();

執行後果:

OnCompleted中的代碼會在義務執行完成之後執行!

另外task.ContinueWith()也是一個重要的辦法:

Console.WriteLine("主線程開端");
Task<string> task = Task<string>.Run(() => {
    Thread.Sleep(2000); 
    return Thread.CurrentThread.ManagedThreadId.ToString(); 
});

task.GetAwaiter().OnCompleted(() =>
{
    Console.WriteLine(task.Result);
});
task.ContinueWith(m=>{Console.WriteLine("第一個義務完畢啦!我是第二個義務");});
Console.WriteLine("主線程完畢");
Console.Read();

執行後果:

ContinueWith()辦法可以讓該後台線程持續執行新的義務。

Task的運用還是比擬靈敏的,大家可以研討下,好了,以上就是全部內容了,篇幅和才能都無限,希望對大家有用!

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved