Zakleszczyłem się asynciem

0

Cześć, wywołuję sobie asynchronicznie metody i coś mi się zakleszczyło. Póki co, nie ogarnąłem dlaczego tak się dzieje.

Problem dotyczy projektu WinForms.
Wszystko zaczyna się od metody Main.

Mam tam następujące wywołanie:

bool res = Task.Run(() => contr.RegisterPremiseAllInOne()).Result;

Wywołuję po prostu metodę asynchroniczną RegisterPremiseAllInOne w nowym Tasku.

Co się dzieje dalej. Metoda RegisterPremiseAllInOne działa mniej więcej tak (uproszczony model):

public async Task<bool> RegisterPremiseAllInOne()
{
   AppUser loggedAppUser = await appUserContr.GetLoggedAppUser(); // <-- tu wszystko jest ok
   if(loggedAppUser == null)
      return false;
  
   var premises = await GetPremisesForLoggedUser(); //tu na końcu jest zakleszczenie
}

I teraz pokażę co wywołują poszczególne metody (wszystko rozbija się o zapytania RESTowe WebAPI):
GetLoggedAppUser -> await API.Users.GetLoggedAppUser() -> await ClientHelper.GetObjectFromRequest<AppUser>("AppUsers/logged") -> await SendAsyncRequest() -> await HttpClient.SendAsync()

To powinno być w miarę czytelne.
I tu jest wszystko OK. Co ciekawe, metoda GetPremisesForLoggedUser jest częściowo zbieżna z tą i wygląda tak:
GetPremisesForLoggedUser -> await API.Premises.GetForLoggedUser() -> await ClientHelper.GetListFromRequest<Premise>("premises/logged") -> await SendAsyncRequest() -> await HttpClient.SendAsync()

Jak widać droga jest prosta. Wywołanie metody z API, które na końcu po prostu posyła requesta.

Wszystko wiesza się podczas wywołania SendAsync z HttpClient. Nie mam zielonego pojęcia dlaczego. W końcu pierwsza ścieżka (GetLoggedAppUser) działa ok.
Po stronie serwera też wszystko jest OK. Serwer zwraca poprawną odpowiedź. A klient jak wisiał, tak wisi.

Wg mnie to wszystko powinno zadziałać. Nie mieszam przecież kodu synchronicznego z asynchronicznym. Wszystkie te metody zwracają Task<T> i są oznaczone jako async. Więc gdzie może leżeć problem? A może chcecie zobaczyć screeny z okien debug Parallel Stack / Tasks?

1

OK, ludzie na stacku mnie trochę natchnęli do rozwiązania tego problemu. To był jednak standardowy deadlock na wątku UI, ale nieco bardziej zakamuflowany.
Zaraz przed wywołaniem

var premises = await GetPremisesForLoggedUser();

tworzyłem formę za pomocą IoC:

IPremisePickerView view = objFactory.Resolve<IPremisePickerView>();
var premises = await GetPremisesForLoggedUser();

I tu powstał problem. Wątek, na którym tworzona jest forma staje się automatycznie wątkiem UI. Więc await GetPremisesForLoggedUser() był wywoływany już z wątku UI. To oznacza, że Task utworzony w Main od pewnego momentu pracował na wątku UI. Dlatego też Task.Run.Result prawdopodobnie zakleszczył się.

Rozwiązaniem było przesunięcie utworzenia formy za wywołanie metody:

var premises = await GetPremisesForLoggedUser();
IPremisePickerView view = objFactory.Resolve<IPremisePickerView>();

Dzięki temu metoda jest wywoływana jeszcze w wątku, który nie jest wątkiem UI.

1 użytkowników online, w tym zalogowanych: 0, gości: 1