C#でのThread、Task、Async/Await、IAsyncResultのグラフィックコードの詳細説明

黄舟
リリース: 2017-03-29 11:28:10
オリジナル
3291 人が閲覧しました

この記事では主に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スレッドプール

想像してみてください。 Web サイトのバックグラウンドで 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 セマフォ

セマフォはスレッドの調整を担当し、特定のリソースにアクセスするスレッドの数を制限できます

以下に 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();
}
ログイン後にコピー

実行結果は次のとおりです。

ご覧のとおり、最初は 3 つのスレッドだけが実行され、1 つのスレッドが実行されて解放されると、新しいスレッドが実行されます。

SemaphoreSlim クラスに加えて、より柔軟な感じの Semaphore クラス。ここでは説明しません。

タスクは .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 がない場合は非同期で実行されます。

タスクとスレッドの比較:

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

Task は戻り値を持つ 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 タスクは cancelTokenSource クラスを通じてキャンセルできますが、あまり使用されていないと思いますので、使用方法は比較的簡単です。興味がある方は検索してみてください。

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 である必要があります。

await必须用来修饰Task或Task,而且只能出现在已经用async关键字修饰的异步方法中。通常情况下,async/await成对出现才有意义,

看看运行结果:

可以看出来,main函数调用GetStrLengthAsync方法后,在await之前,都是同步执行的,直到遇到await关键字,main函数才返回继续执行。

那么是否是在遇到await关键字的时候程序自动开启了一个后台线程去执行GetString方法呢?

现在把GetString方法中的那行注释加上,运行的结果是:

大家可以看到,在遇到await关键字后,没有继续执行GetStrLengthAsync方法后面的操作,也没有马上反回到main函数中,而是执行了GetString的第一行,以此可以判断await这里并没有开启新的线程去执行GetString方法,而是以同步的方式让GetString方法执行,等到执行到GetString方法中的Task.Run()的时候才由Task开启了后台线程!

那么await的作用是什么呢?

可以从字面上理解,上面提到task.wait可以让主线程等待后台线程执行完毕,await和wait类似,同样是等待,等待Task.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

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);
ログイン後にコピー

以上がC#でのThread、Task、Async/Await、IAsyncResultのグラフィックコードの詳細説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート