<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          async、await是如何簡化了C#中多線程的開發(fā)過程

          共 7666字,需瀏覽 16分鐘

           ·

          2020-08-06 22:23



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

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

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

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

          讓我們舉個例子。假設(shè)我們有一個.NET Core項目,我們應(yīng)該在其中管理三個實體: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; }}

          現(xiàn)在假設(shè)我們應(yīng)該使用Entity Framework Core將這些實體的值保存在數(shù)據(jù)庫中。其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"}); }}

          從代碼中可以看到,我們插入了一些示例數(shù)據(jù)進行處理?,F(xiàn)在假設(shè)我們要使用Controller API公開這些數(shù)據(jù),既單獨(針對每個實體),又使用將它們?nèi)柯?lián)接在一起的方法,并通過一次調(diào)用返回它們。

          使用同步方法,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()方法在其中調(diào)用返回單個結(jié)果的三個方法,并等待每個方法的執(zhí)行完成后再傳遞到下一個結(jié)果。這三種方法互不相關(guān),因此您無需等待其中一種方法的執(zhí)行即可調(diào)用另一種方法。然后,您可以創(chuàng)建三個獨立的任務(wù)以并行執(zhí)行。
          第一種方法可以基于該方法
          Task.Run()作業(yè)運行在線程池之上,并返回一個任務(wù)對象,它代表了這項工作。這樣,方法可以在線程池的不同線程上同時運行:

          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屬性包含詳細說明的結(jié)果。方法WhenAll允許暫停當(dāng)前線程執(zhí)行,直到所有Task完成。運行代碼,我們可以注意到一個有趣的事情:調(diào)用中斷,并啟動以下異常:

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

          此錯誤消息告訴我們,方法在不同的線程上同時執(zhí)行,但是由于它們使用與DbContext?相同的實例來連接數(shù)據(jù)庫, 因此引發(fā)了異常,DbContext類無法確保線程安全的功能:我們可以輕松地繞過此問題,避免了.NET Core 的依賴注入引擎創(chuàng)建單個實例,而我們?yōu)槊糠N方法創(chuàng)建了單獨的實例。作為示例,讓我們看看方法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(); } }}

          好吧,現(xiàn)在可以了。我們應(yīng)該注意,EFCore提供了一些方法,例如,與方法ToArrayAsync一樣,使用相同的DbContext進行異步調(diào)用,該方法從IQueryable 創(chuàng)建一個數(shù)組,該數(shù)組? 異步枚舉它。此方法返回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尚未設(shè)計為線程安全的。您可以查詢此鏈接以獲取更多信息:https?:?//docs.microsoft.com/zh-cn/ef/core/querying/async

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

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

          [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()返回的是數(shù)組而不是Task。實際上,這里我們需要三個線程:主調(diào)用方(Get()),數(shù)據(jù)庫查詢(this.db.Companies.ToListAsync())和一個線程,該線程將一個值添加到列表中。我們有三種方法可以做到這一點:讓我們用三種單一方法來查看它們。我們已經(jīng)看到的第一個,可以使用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(),該方法可以應(yīng)用于任務(wù),并且可以在上一個方法完成后立即指定要運行的新任務(wù):

          [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();              });     }}

          我們可以讓編譯器執(zhí)行“垃圾代碼”,并使用關(guān)鍵字asyncawait,這可以為我們創(chuàng)建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的創(chuàng)建,從而使我們可以異步返回。讓我們想象一下一個場景,其中調(diào)用不止一個,并且這種方法如何使一切變得更加線性。

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

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

          示例代碼見:

          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


          往期推薦

          強烈推薦:這套.Net Core開源內(nèi)容管理系統(tǒng)
          10大程序員必逛網(wǎng)站,良心推薦,建議收藏!
          MySQL 的這個 BUG,坑了多少人?
          回復(fù)?【關(guān)閉】學(xué)關(guān)
          回復(fù)?【實戰(zhàn)】獲取20套實戰(zhàn)源碼
          回復(fù)?【福利】獲取最新微信支付有獎勵
          回復(fù)?【被刪】學(xué)
          回復(fù)?【訪客】學(xué)
          回復(fù)?【卡通】學(xué)制作微信卡通頭像
          回復(fù)?【python】學(xué)微獲取全套0基礎(chǔ)Python知識手冊
          回復(fù)?【2019】獲取2019 .NET 開發(fā)者峰會資料PPT
          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  精品人人妻 | 影音先锋午夜性爱av | 久久鸡巴视频 | 国语对白乱妇激情视频 | 日韩精品在线观看高清 |