一個例子形象的理解協程和執行緒的區別
Talk is cheap, show me the code! 所以,廢話先不說,先上代碼:
首先寫一個WebAPI介面
/// <summary>
/// 測驗介面
/// </summary>
[RoutePrefix("api/test")]
public class TestController : ApiController
{
/// <summary>
/// 測驗GET請求
/// </summary>
/// <param name="val">測驗引數</param>
[HttpGet]
[Route("TestGet")]
public HttpResponseMessage TestGet(string val)
{
Thread.Sleep(200); //模擬執行耗時操作
return new HttpResponseMessage { Content = new StringContent(val.ToString(), Encoding.UTF8, "text/plain") };
}
}
測驗代碼
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
namespace AsyncDemo2
{
public partial class Form1 : Form
{
private int n = 200;
public Form1()
{
InitializeComponent();
Task.Factory.StartNew(() =>
{
while (true)
{
Thread.Sleep(100);
ThreadPool.GetMaxThreads(out int w1, out int c1);
ThreadPool.GetAvailableThreads(out int w2, out int c2);
int w = w1 - w2;
int c = c1 - c2;
label1.BeginInvoke(new Action(() =>
{
label1.Text = string.Format("作業執行緒:{0} 異步執行緒:{1}", w, c);
}));
}
}, TaskCreationOptions.LongRunning);
}
/// <summary>
/// 日志輸出
/// </summary>
private void Log(string msg)
{
this.BeginInvoke(new Action(() =>
{
textBox1.AppendText(DateTime.Now.ToString("mm:ss.fff") + ":" + msg + "\r\n");
}));
}
/// <summary>
/// 異步請求
/// </summary>
private async Task ReqeustAsync(int val)
{
try
{
Log("異步 開始請求" + val);
string result = await HttpUtil.HttpGetAsync("http://localhost:8500/api/test/TestGet?val=" + val);
Log("異步 回傳資料" + result + " 執行緒ID:" + Thread.CurrentThread.ManagedThreadId);
}
catch (Exception ex)
{
Log("出錯:" + ex.Message);
}
}
/// <summary>
/// 在執行緒中同步請求
/// </summary>
private Task Request(int val)
{
return Task.Run(() =>
{
try
{
Log("同步多執行緒 開始請求" + val);
string result = HttpUtil.HttpGet("http://localhost:8500/api/test/TestGet?val=" + val);
Log("同步多執行緒 回傳資料" + result + " 執行緒ID:" + Thread.CurrentThread.ManagedThreadId);
}
catch (Exception ex)
{
Log("出錯:" + ex.Message);
}
});
}
//測驗異步請求
private async void button3_Click(object sender, EventArgs e)
{
textBox1.Text = string.Empty;
Stopwatch sw = new Stopwatch();
List<Task> taskList = new List<Task>();
sw.Start();
for (int i = 0; i < n; i++)
{
Task t = ReqeustAsync(i);
taskList.Add(t);
}
foreach (Task t in taskList)
{
await t;
}
Log(n + "個異步請求完成,耗時:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
sw.Stop();
}
//測驗多執行緒同步請求
private void button4_Click(object sender, EventArgs e)
{
textBox1.Text = string.Empty;
Task.Run(() =>
{
List<Task> taskList = new List<Task>();
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < n; i++)
{
Task t = Request(i);
taskList.Add(t);
}
Task.WaitAll(taskList.ToArray());
Log(n + "個多執行緒同步請求完成,耗時:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
sw.Stop();
});
}
}
}
測驗結果

性能差9倍!
把WebAPI介面中模擬執行耗時操作改成1000毫秒再測驗,測驗結果如下:

性能差10倍!
把Form1.cs建構式中添加一行ThreadPool.SetMinThreads(20, 20);再測:

設定執行緒池中執行緒的最小數量為20后,性能差距縮小了,性能只差4倍!為什么?沒有設定執行緒池最小數量時,大約每1秒增加1到2個執行緒,執行緒增加速度太慢了,不影響協程性能,協程只需要很少的執行緒數量,但影響多執行緒性能,
把Form1.cs建構式中代碼修改成ThreadPool.SetMinThreads(200, 200);再測:

當執行緒池中執行緒數量足夠多時,性能差不多了!
結論
通過這個形象的例子,你體會到協程的好處了嗎?
有人可能會說,你怎么不把WebAPI端改成異步試試?WebAPI端是模擬的操作,在沒有外部操作(IO操作、資料庫操作等),僅有資料計算時,WebAPI端改成異步沒區別,
有一個截圖中沒有體驗出來的,測驗程序中,對于協程測驗,作業執行緒和異步執行緒始終為0,我想異步執行緒應該是變化的,可能只是變化太快,看不出來,而多執行緒測驗,測驗程序中,我們可以看到作業執行緒的數量是大于0的,維持在一定數量,直到請求完成,也就是說,測驗程序中,要占用一定數量的作業執行緒,
所以結論是什么?
協程在執行耗時請求時,不會占用執行緒(注意占用這個詞,它肯定是使用執行緒的,但不會在耗時請求程序中占用),在執行緒池中執行緒數量較少時,協程的性能比多執行緒好很多,想一想,要是IO操作、資料庫操作,存在一些慢查詢、超時的,如果你使用多執行緒,你的執行緒池就爆了,協程就不會(Talk is cheap, show me the code!),后面附上測驗,
WebAPI服務端補充說明
上面的測驗,服務端我忘了說了,服務端啟動服務前,我加了一行代碼ThreadPool.SetMinThreads(200, 200);,因為你測驗客戶端之前,服務端性能要跟上,不然測了個寂寞,
如果我把這行代碼刪掉,預熱后,再測:

可以看到差距只有2.5倍了!因為服務端執行緒數量此時是1秒增加1、2個執行緒,服務端性能跟不上,客戶端的異步請求自然也快不起來,
爆執行緒池測驗
測驗前修改:
- 把WebAPI介面中模擬執行耗時操作改成2000000毫秒,模擬服務端性能不行,反應慢,
- ThreadPool.SetMinThreads(20, 20);客戶端執行緒池最小執行緒數量設定為20,當然執行緒池越大越不容易爆,這里為了更快重現出來,所以設定小一點,注意,我可沒有設定執行緒池上限!只是設定了下限,
測驗視頻:
注意看測驗時作業執行緒數量:

說明:協程,不論什么時候點,都會有回應,當然可能后面點多了會報錯,但即使報錯,回應是有的,而多執行緒,后面點的,回應就很慢了,

你們可能會說你設定的執行緒池最小執行緒數量太小,改成ThreadPool.SetMinThreads(200, 200);,再測:

注意看作業執行緒數量!
附
WebAPI服務啟動代碼:
protected override void OnStart(string[] args)
{
ThreadPool.SetMinThreads(200, 200);
int port = int.Parse(ConfigurationManager.AppSettings["WebApiServicePort"]);
StartOptions options = new StartOptions();
options.Urls.Add("http://127.0.0.1:" + port);
options.Urls.Add("http://localhost:" + port);
options.Urls.Add("http://+:" + port);
WebApp.Start<Startup>(options);
LogUtil.Log("Web API 服務 啟動成功");
}
HttpUtil代碼:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// Http上傳下載檔案
/// </summary>
public class HttpUtil
{
/// <summary>
/// HttpGet
/// </summary>
/// <param name="url">url路徑名稱</param>
/// <param name="cookie">cookie</param>
public static async Task<string> HttpGetAsync(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
{
// 設定引數
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.CookieContainer = cookie;
request.Method = "GET";
request.ContentType = "text/plain;charset=utf-8";
request.Timeout = Timeout.Infinite;
if (headers != null)
{
foreach (string key in headers.Keys)
{
request.Headers.Add(key, headers[key]);
}
}
//發送請求并獲取相應回應資料
HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync());
//直到request.GetResponse()程式才開始向目標網頁發送Post請求
Stream instream = response.GetResponseStream();
StreamReader sr = new StreamReader(instream, Encoding.UTF8);
//回傳結果網頁(html)代碼
string content = await sr.ReadToEndAsync();
instream.Close();
return content;
}
/// <summary>
/// HttpGet
/// </summary>
/// <param name="url">url路徑名稱</param>
/// <param name="cookie">cookie</param>
public static string HttpGet(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
{
// 設定引數
HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
request.CookieContainer = cookie;
request.Method = "GET";
request.ContentType = "text/plain;charset=utf-8";
request.Timeout = Timeout.Infinite;
if (headers != null)
{
foreach (string key in headers.Keys)
{
request.Headers.Add(key, headers[key]);
}
}
//發送請求并獲取相應回應資料
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//直到request.GetResponse()程式才開始向目標網頁發送Post請求
Stream instream = response.GetResponseStream();
StreamReader sr = new StreamReader(instream, Encoding.UTF8);
//回傳結果網頁(html)代碼
string content = sr.ReadToEnd();
instream.Close();
return content;
}
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/502088.html
標籤:.NET技术
