日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

一文看懂async和“await”关键词是如何简化了C#中多线程的开发过程

發布時間:2023/12/4 C# 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一文看懂async和“await”关键词是如何简化了C#中多线程的开发过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一文看懂"async"和“await”關鍵詞是如何簡化了C#中多線程的開發過程

當我們使用需要長時間運行的方法(即,用于讀取大文件或從網絡下載大量資源)時,在同步的應用程序中,應用程序本身將停止運行,直到活動完成。在這些情況下,異步編程非常有用:它使我們能夠并行執行不同任務,并在需要時等待其完成。

這種方法有許多不同的模型類型:APM(異步編程模型),基于事件(異步模型EAP),以及TAP,基于任務的(異步模型任務)。讓我們看看如何使用關鍵字async和await在C#中實現第三個方法。

編寫異步代碼的主要問題之一是可維護性:實際上,許多人普遍認為這種編程方法會使代碼復雜化。幸運的是,C#5引入了一種簡化的方法,在該方法中,編譯器運行由開發人員先前完成的艱巨任務,并且應用程序保留類似于同步代碼的邏輯結構。

讓我們舉個例子。假設我們有一個.NET Core項目,我們應該在其中管理三個實體:Area,Company和Resource。

public class Area {public int Id { get; set; }[Required][StringLength(255)]public string Name { get; set; } }public class Company {public int Id { get; set; }[Required][StringLength(255)]public string Name { get; set; } }public class Resource {public int Id { get; set; }[Required][StringLength(255)]public string Name { get; set; } }

現在假設我們應該使用Entity Framework Core將這些實體的值保存在數據庫中。其DbContext是:

public class AppDbContext : DbContext {public DbSet<Area> Areas { get; set; }public DbSet<Company> Companies { get; set; }public DbSet<Resource> Resources { get; set; }public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) {}override protected void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Area> ().HasData(new Area { Id = 1, Name = "Area1"},new Area { Id = 2, Name = "Area2"},new Area { Id = 3, Name = "Area3"},new Area { Id = 4, Name = "Area4"},new Area { Id = 5, Name = "Area5"});modelBuilder.Entity<Company> ().HasData(new Area { Id = 1, Name = "Company1"},new Area { Id = 2, Name = "Company2"},new Area { Id = 3, Name = "Company3"},new Area { Id = 4, Name = "Company4"},new Area { Id = 5, Name = "Company5"});modelBuilder.Entity<Resource>().HasData(new Area { Id = 1, Name = "Resource1"},new Area { Id = 2, Name = "Resource2"},new Area { Id = 3, Name = "Resource3"},new Area { Id = 4, Name = "Resource4"},new Area { Id = 5, Name = "Resource5"});} }

從代碼中可以看到,我們插入了一些示例數據進行處理。現在假設我們要使用Controller API公開這些數據,既單獨(針對每個實體),又使用將它們全部聯接在一起的方法,并通過一次調用返回它們。

使用同步方法,Controller API 將是:

[ApiController] [Route("[controller]")] public class DataController : ControllerBase {private readonly AppDbContext db = null;public DataController(AppDbContext db){this.db = db;}public IActionResult Get(){var areas = this.GetAreas();var companies = this.GetCompanies();var resources = this.GetResources();return Ok(new { areas = areas, companies = companies, resources = resources });}[Route("areas")]public Area[] GetAreas() {return this.db.Areas.ToArray();}[Route("companies")]public Company[] GetCompanies() {return this.db.Companies.ToArray();}[Route("resources")]public Resource[] GetResources() {return this.db.Resources.ToArray();} }

Get()方法在其中調用返回單個結果的三個方法,并等待每個方法的執行完成后再傳遞到下一個結果。這三種方法互不相關,因此您無需等待其中一種方法的執行即可調用另一種方法。然后,您可以創建三個獨立的任務以并行執行。
第一種方法可以基于該方法Task.Run()作業運行在線程池之上,并返回一個任務對象,它代表了這項工作。這樣,方法可以在線程池的不同線程上同時運行:

public IActionResult Get() {var areas = Task.Run(() = > this.GetAreas());var companies = Task.Run(() = > this.GetCompanies());var resources = Task.Run(() = > this.GetResources()); Task.WhenAll(areas, companies, resources);return Ok(new { areas = areas.Result, companies = companies.Result, resources = resources.Result }); }

TaskResult屬性包含詳細說明的結果。方法WhenAll允許暫停當前線程執行,直到所有Task完成。運行代碼,我們可以注意到一個有趣的事情:調用中斷,并啟動以下異常:

AggregateException:發生一個或多個錯誤。(在上一個操作完成之前,第二個操作在此上下文上開始。這通常是由使用相同DbContext實例的不同線程引起的。有關如何避免DbContext線程問題的更多信息,請參見https://go.microsoft.com/fwlink/?linkid=2097913。[1]

此錯誤消息告訴我們,方法在不同的線程上同時執行,但是由于它們使用與DbContext?相同的實例來連接數據庫, 因此引發了異常,DbContext類無法確保線程安全的功能:我們可以輕松地繞過此問題,避免了.NET Core 的依賴注入引擎創建單個實例,而我們為每種方法創建了單獨的實例。作為示例,讓我們看看方法GetAreas()會如何變化:

public class DataController : ControllerBase {private readonly DbContextOptionsBuilder <AppDbContext> optionsBuilder = null;public DataController(IConfiguration configuration){this.optionsBuilder = new DbContextOptionsBuilder <AppDbContext> ().UseSqlite(configuration.GetConnectionString("DefaultConnection"));}[Route("areas")]public Area[] GetAreas() {using(var db = new AppDbContext(this.optionsBuilder.Options)){return db.Areas.ToArray();}} }

好吧,現在可以了。我們應該注意,EFCore提供了一些方法,例如,與方法ToArrayAsync一樣,使用相同的DbContext進行異步調用,該方法從IQueryable 創建一個數組,該數組? 異步枚舉它。此方法返回Task ,它是表示異步操作的活動。

這樣,我們不再需要使用Task.Run():?

public IActionResult Get() {var areas = this.GetAreas();var companies = this.GetCompanies();var resources = this.GetResources();Task.WhenAll(areas, companies, resources);return Ok(new { areas = areas.Result, companies = companies.Result, resources = resources.Result }); }[Route("areas")] public Task<Area[]> GetAreas() {return db.Areas.ToArrayAsync(); }

無論如何,Microsoft不能保證這些異步方法在每種情況下都能工作,因為DbContext尚未設計為線程安全的。您可以查詢此鏈接以獲取更多信息:https?:?//docs.microsoft.com/zh-cn/ef/core/querying/async

使用Entity Framework Core時,最佳實踐是在啟動另一個異步操作之前,為每個異步操作都擁有一個DbContext或等待每個異步操作完成。當我們必須進行異步調用并返回結果時,這種最佳做法是可以的。

但是,如果我們想在返回結果之前對結果進行一些操作,會發生什么?如果我們想向列表中添加元素怎么辦?我們應該等待結果,添加元素,然后返回修改后的列表:

[Route("companies")] public Task<Company[]> GetCompanies() {using (var db = new AppDbContext(this.optionsBuilder.Options)){var data = this.db.Companies.ToListAsync().Result;data.Insert(0, new Company() { Id = 0, Name = "-"});return data.ToArray();} }

不幸的是,該代碼無法編譯,因為data.ToArray()返回的是數組而不是Task。實際上,這里我們需要三個線程:主調用方(Get()),數據庫查詢(this.db.Companies.ToListAsync())和一個線程,該線程將一個值添加到列表中。我們有三種方法可以做到這一點:讓我們用三種單一方法來查看它們。我們已經看到的第一個,可以使用Task.Run()方法:

[Route("companies")] public Task<Company[]> GetCompanies() {return Task.Run(() =>{using (var db = new AppDbContext(this.optionsBuilder.Options)){var data = db.Companies.ToList();data.Insert(0, new Company() { Id = 0, Name = "-" });return data.ToArray();}}); }

作為替代方案,我們可以使用方法ContinueWith(),該方法可以應用于任務,并且可以在上一個方法完成后立即指定要運行的新任務:

[Route("resources")] public Task <Resource[]> GetResources() {using (var db = new AppDbContext(this.optionsBuilder.Options)){return db.Resources.ToListAsync().ContinueWith(dataTask = >{var data = dataTask.Result;dataTask.Result.Insert(0, new Resource() { Id = 0, Name = "-" });return data.ToArray();});} }

我們可以讓編譯器執行“垃圾代碼”,并使用關鍵字asyncawait,這可以為我們創建Task:

[Route("areas")] public async Task <Area[]> GetAreas() {using (var db = new AppDbContext(this.optionsBuilder.Options)){var data = await db.Areas.ToListAsync();data.Insert(0, new Area() { Id = 0, Name = "-" });return data.ToArray();} }

正如您在最后一種方法中看到的那樣,代碼更加簡單,并且向我們隱藏了Task的創建,從而使我們可以異步返回。讓我們想象一下一個場景,其中調用不止一個,并且這種方法如何使一切變得更加線性。

重構的作用是方法GetAreas()已成為異步操作。這個事實意味著,當不同的請求到達此API時,分配給該請求的線程池的線程將被釋放以供其他請求使用,直到DbContext終止數據提取為止。

我希望我能引起您足夠的興趣來深入分析該論點。在許多情況下,使用async和await非常方便,并且除了使代碼更加簡潔和線性外,還可以提高一般應用程序的性能。

示例代碼見:

https://github.com/fvastarella/Programmazione-asincrona-con-async-await

References

[1]?https:?https://docs.microsoft.com/en-us/ef/core/querying/async
[2]?//docs.microsoft.com/zh-cn/ef/core/querying/async:?https://docs.microsoft.com/en-us/ef/core/querying/async

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的一文看懂async和“await”关键词是如何简化了C#中多线程的开发过程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。