Ich möchte wissen, ob es möglich ist, den UI-Thread-Kontext zu erhalten, wenn die Async / await-Methode verwendet wird. Wenn ja, wie?

Mit WPF / MVVM-Muster geschriebener Code:

long asyncTimeChecker = 0;
int prevSec2 = 0;

/// <summary>
/// Button Click Command
/// </summary>
private void ExeTimerActivate(object o)
{
   if (!IsTimerStart)
   {
      ActivateAsyncTicToc();
      TimerState = "Stop";
   }
   else if (IsTimerStart)
   {
      ActivateAsyncTicToc();
      TimerState = "Start";
   }

   IsTimerStart = !IsTimerStart;
}        

/// <summary>
/// Call Method By Async/Await
/// </summary>
private async void ActivateAsyncTicToc()
{
  IsTimerStartAsync = !IsTimerStartAsync;

  var task1 = Task.Run(() => AsyncTicToc());
  await task1;
}



/// <summary>
/// I tried to UI access by other thread what use Async/Await
/// </summary>
private void AsyncTicToc()
{
  while (IsTimerStartAsync)
  {
      System.Threading.Thread.Sleep(10);
      AsyncTimeText = $"{asyncTimeChecker / 1000}.{asyncTimeChecker % 1000}";
      asyncTimeChecker += 10;

      /// ========================================
      /// This Position Get CrossThread Problem
      /// ========================================
      if (prevSec2 < asyncTimeChecker / 1000)
      {
           prevSec2++;
           if (TimerColor2.Color == Colors.Blue)
               TimerColor2.Color = Colors.Red;
           else
               TimerColor2.Color = Colors.Blue;
       }
   }

}

Ich weiß, dass wir den UI-Thread mit Dispatcher abrufen können, möchte aber wissen, ob dies mit async / await möglich ist.

-1
M.JiHong 18 Jän. 2019 im 05:11

3 Antworten

Beste Antwort

Sie schließen Ihren async -Aufruf in eine Aufgabe ein. Dies besiegt die gesamte Statemaschine und führt dazu, dass unnötigerweise ein Threadpool-Thread verwendet wird.

Der Aufruf und alle Fortsetzungen enden in einem anderen Thread und können daher nicht ohne Marshalling auf die Benutzeroberfläche zugreifen

Zumindest sollten Sie dies tun, wenn Sie unbedingt ein async void verwenden müssen, das nur auf die Aufgabe wartet und sie nicht umschließt

private async void ActivateAsyncTicToc()
{
   try
   {
      IsTimerStartAsync = !IsTimerStartAsync;    
      await AsyncTicToc();
   }
   catch (Exception e)
   {
      // make sure you observe exceptions in async void
   }
}

Noch besser, lassen Sie die async mit async Task verbreiten

private async Task ActivateAsyncTicToc()
{
   IsTimerStartAsync = !IsTimerStartAsync;    
   await AsyncTicToc();
}

Was ohnehin verdächtig aussieht, sollten Sie wahrscheinlich tun (Eliding async und await) und die Aufgabe nur an eine andere Person weitergeben, die auf Sie wartet. Dies ist ein kleiner Leistungsgewinn

private Task ActivateAsyncTicToc()
{
   IsTimerStartAsync = !IsTimerStartAsync;    
   return AsyncTicToc();
}

Tatsächlich ist dies ein Minenfeld und ich bin den ganzen Tag hier

Sie müssen anfangen, über async und await zu lesen

1
Michael Randall 18 Jän. 2019 im 02:30

Zunächst können Sie await Task.Delay(10) anstelle von Thread.Sleep(10) verwenden und Ihre AsyncTicToc -Methode in async Task ändern, ohne einen Thread zu starten. Dadurch wird das Steuerelement im UI-Thread geändert (der Kontext, aus dem Sie gewartet haben).

Zweitens, um die Benutzeroberfläche von anderen Threads zu aktualisieren, können Sie die Abstraktion darüber verwenden, wenn Sie den Dispatcher nicht direkt verwenden möchten: SynchronizationContext -Klasse. Weitere Informationen finden Sie hier.

Beispiel: SynchronizationContext.Current.Post(action, actionParameter);

Drittens ist es besser, ein Ansichtsmodell und eine Bindung zu verwenden. Wenn Sie WPF verwenden, werden die Threads für Eigenschaftsänderungen für Sie synchronisiert.

0
haimb 18 Jän. 2019 im 06:37

Es ist zwar möglich, mit Dispatcher oder SynchronizationContext von einem anderen Thread zum UI-Thread zu gelangen, ich empfehle jedoch dringend, dies nicht zu tun. Dies liegt daran, dass Ihre Logik dadurch weniger testbar und stärker an die Umgebung gebunden ist. Im Fall von Dispatcher ist es stark an die Ausführung in einer WPF-Umgebung gebunden. SynchronizationContext ist besser, aber es ist immer noch an das Ausführen in einer Umgebung gebunden.

Mit dieser Art von Ansatz haben Sie eine Abhängigkeit von Ihrer Logik von einem UI-Thread wie diesem:

UI-Code => Hintergrund-Thread-Logik => UI-Thread

Verwenden Sie stattdessen IProgress<T> und Progress<T> aus Ihrer Hintergrund-Thread-Logik, um Fortschrittsberichte an den Aufrufer zurückzusenden, der entscheidet, wie diese Fortschrittsberichte angezeigt werden. Dann sieht Ihre Abhängigkeit folgendermaßen aus:

UI-Code => Hintergrund-Thread-Logik

Und Ihre Hintergrund-Thread-Logik hängt nicht davon ab, dass ein UI-Thread vorhanden ist. Dies macht es resuierbarer und testbarer.

0
Stephen Cleary 27 Jän. 2019 im 02:15