当一切都陷入 Blazor WASM 时

问题描述 投票:0回答:1

我在 Blazor 页面中有下一个代码。它在 OnParametrSetAsync() 中调用。我只使用了异步等待。没有 Task.Wait() 或 Task.Result。

   private async Task SelectNewAddress(AddressElement elem, bool withFillParam)
  {


          foreach (var fieldSource in elem.DicFields.Values)
          {
              if (fieldSource.Id > 0)
                  address.SelectAddressField(fieldSource);
          }

          var lowLevelElementKey = address.DicFields.Where(p => p.Value != null && p.Value.Id > 0).Max(p => p.Key);

          List<Task> listTasks = new List<Task>();

          if (withFillParam)
          {
              var t = FillParams();
              listTasks.Add(t);
          }

          foreach (var kkey in address.DicFields.Keys)
          {
              var field = address.DicFields[kkey];

              if (field.Id > 0 && field.IdParent > 0)
              {
                  var t1 = FillDataSourceForElem(field);

                  listTasks.Add(t1);

                  if (kkey == lowLevelElementKey)
                  {
                      var t2 = Task.Run(async () =>
                       {
                           if (field.Level >= 1 && field.Level <= 8)
                           {
                               await FillAllChilds(field.Id);
                           }
                           else if (field.Level == 10)
                           {
                               await FillAppartments(field.Id);
                               await FillCarPlaces(field.Id);
                           }
                           else if (field.Level == 11)
                           {
                               await FillRooms(field.Id);
                           }
                       });
                      listTasks.Add(t2);
                  }
              }
          }
          Console.WriteLine("before when all");
          //stack here, but only in browser and only 1 time after start server. Working 
          //fine in MAUI Blazor
          await Task.WhenAll(listTasks.ToArray()).ConfigureAwait(false);
          Console.WriteLine("after when all");
          InvokeAsync(() => { StateHasChanged(); });
          //_ = Task.Run(async () =>
          // {
          //     await Task.WhenAll(listTasks.ToArray());
          //     await InvokeAsync(() => { StateHasChanged(); });

          // });

  }

await Task.WhenAll(listTasks.ToArray()) 每次使用此代码打开页面时都会卡住,但仅在启动服务器后第一次。如果我关闭浏览器页面并重新打开它,代码将不会卡住并且可以正常工作。相同的代码在 Blazor MAUI 中运行良好。我以为是死锁,然后添加ConfigureAwait(false),但没有帮助。如果我像评论中那样触发并忘记 WhenAll ,它也可以正常工作,但在操作完成之前我需要块接口。在每个函数 FillDataSourceForElem、FillAllChilds 等中,只需调用 wait _httpClient.GetAsync()。服务器端工作正常,返回响应快。

如果我删除附加任务和 WhenAll 并尝试等待每个函数(FillDataSourceForElem、FillAllChilds 等),我将停留在 wait _httpClient.GetAsync() 上,但每次我都会停留在不同的函数上。第一次在 FillDataSourceForElem 中,下次在 FillAllChilds 等中。 现在我不知道为什么会这样。

看起来像死锁...

在F12中我看到浏览器确实发出了请求,但没有得到响应(状态等待)。然后 WhenAll 卡住了,因为任务无法完成,但为什么它只发生 1 次,为什么它在 MAUI 中工作以及为什么 GetAsync 卡住了(如果我在控制器的服务器端做了断点,我看到控制器方法确实返回结果)。 如果我触发并忘记(如评论中所示),我会看到所有请求都执行得很快,并从服务器获取结果,并在 _ = Task.Run 中等待 WhenAll() 将会很好地完成。

如果有人知道原因,我将不胜感激。

我使用的是httpClient,像这样初始化吗

     HttpClient _client;
     protected override async System.Threading.Tasks.Task  OnInitializedAsync()
     {
         _client = ClientFactory.CreateClient("clientName");
    
     }

编辑: 完整的调用堆栈: 在 Blazor 组件中

 protected override async Task OnParametersSetAsync()
 {
     await Initialise();
 }

然后

 private async Task Initialise()
 {

     var result = await GetAddressFromDB();
 }

然后

private async Task<bool> GetAddressFromDB()
 {
     try
     {
         var elem = await _client.GetFromJsonAsync<AddressElement>($"url?idForm={IdForm}");
         if (elem is null)
             return false;


             if (elem.Region is null)
                 return false;
             if (elem.Region.Id <= 0)
                 return false;

             address.UseMun = elem.UseMun;
             IsMunHierarchy = address.UseMun;

             await SelectNewAddress(elem, false);
             elem.RajonTown.Copy(address.RajonTown);
            
         return true;

     }
     catch (Exception ex)
     {
         //log error
     }
     return false;
 }

---

 private async Task FillDataSourceForElem(AddressField elem)
   {

       List<AddressField> fields = new List<AddressField>();
       if (elem.Level >= 1 && elem.Level <= 8)
       {

           var rez = await SearchChildsElements(elem.IdParent);
           fields = rez.Where(p => p.Level == elem.Level).ToList();

       }
       else if (elem.Level == 9)
       {

           fields = await GetSteads(elem.IdParent);

       }
       else if (elem.Level == 10)
       {

           var rez = await GetHouses(elem.IdParent);
           fields = rez.Select(x => { AddressField b = x; return b; }).ToList();

       }
       else if (elem.Level == 11)
       {

           fields = await GetAppartments(elem.IdParent);

       }
       else if (elem.Level == 12)
       {

           fields = await GetRooms(elem.IdParent);

       }
       else if (elem.Level == 17)
       {

           fields = await GetCarPlaces(elem.IdParent);

       }


       foreach (var fieldChild in fields)
       {
           address.AddAddreesFieldInSameLevel(fieldChild);
       }

   }

  private async Task<List<AddressField>> SearchChildsElements(long idParent)
  {
      List<AddressField> result = null;
      try
      {
          result = await _client.GetFromJsonAsync<List<AddressField>>($"url?id={idParent}&useMun={_isMunHierarchy}");
      }
      catch (Exception ex)
      {

          //log here
      }
      if (result is null)
          return new List<AddressField>();

      return result;
  }
c# blazor-webassembly
1个回答
-1
投票

我相信你遇到了僵局。

我了解您想要阻止 UI,直到

WhenAll()
中的所有工作完成。为了实现这一点,您可以调用在 UI 线程上执行的
await Task.WhenAll(...)

你是对的,这会阻塞 UI 线程。基本思想是 UI 线程不是普通线程,而是具有某种形式的同步上下文,强制它一次执行一项任务。当它被阻塞等待时,它无法处理 WhenAll 的结果,因此,就会出现死锁。 这里有更好的解释。

您没有发布如何从 UI 线程调用

SelectNewAddress
。我假设您从 UI 线程上的函数调用它并等待它,如下所示:
SelectNewAddress(...).GetAwaiter().GetResult();
。正如上面帖子中所解释的,这将导致死锁。我认为你可以通过将“等待工作”委托给另一个线程来修复它,这样它就不会阻塞 UI 线程。然后,当工作完成时,这个“普通”线程将像您在正常情况下期望的那样继续执行,从而解决您的问题:

Task.Run(() => AsyncFunction(...)).GetAwaiter().GetResult();

这个想法也来自之前提到的帖子。

© www.soinside.com 2019 - 2024. All rights reserved.