<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”關(guān)鍵詞是如何簡化了C#中多線程的開發(fā)過程

          共 7430字,需瀏覽 15分鐘

           ·

          2020-10-10 22:56

          一文看懂"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)第三個方法。

          編寫異步代碼的主要問題之一是可維護性:實際上,許多人普遍認(rè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



          瀏覽 21
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  强欧美小嫩苞第一次免费视频 | 99最新在线视频 | 人人撸人人挺 | 免费大黄网站 | 日韩欧美三级电影在线观看 |