我可以在使用“异步/等待”时获取UI线程上下文吗

时间:2019-01-18 02:11:13

标签: c# wpf multithreading mvvm async-await

我想知道使用Asyn / await方法时是否有可能获得UI线程上下文。如果是,怎么办?

使用WPF / MVVM模式编写的代码:

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;
       }
   }

}

我知道我们可以使用Dispatcher获取UI线程,但想知道是否可以使用async / await。

3 个答案:

答案 0 :(得分:1)

您正在将async调用包装在一个任务中。这将破坏整个状态机,并最终不必要地使用线程池线程。

该调用和任何继续都结束于另一个线程中,因此无法在不进行编组的情况下访问UI

如果您绝对需要使用async void来等待任务,而不包装它,则至少应该这样做

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

更好的是,让async使用async Task

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

无论如何看起来都很可疑,您可能应该这样做(取消asyncawait),然后将任务传递给将要等待的其他人。这是一个很小的性能提升

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

实际上这是一个雷区,并且身份证整天都在这里

您需要开始阅读有关asyncawait的信息

答案 1 :(得分:0)

首先,您可以使用await Task.Delay(10)代替Thread.Sleep(10),然后将AsyncTicToc的方法更改为async Task,而无需启动线程。这将使您可以更改UI线程(等待的上下文)中的控件。

第二,要从其他线程更新UI,如果不想直接使用调度程序,则可以在其上使用抽象:SynchronizationContext类。查看更多信息here

示例:SynchronizationContext.Current.Post(action, actionParameter);

第三,使用视图模型和绑定更为合适。如果使用WPF,它将为您同步线程以进行属性更改。

答案 2 :(得分:0)

尽管可以使用DispatcherSynchronizationContext从另一个线程“伸出”到UI线程,但我强烈建议不要这样做。这是因为它使您的逻辑难以测试,并且与环境紧密相关。对于Dispatcher,它与在WPF环境中运行紧密相关; SynchronizationContext更好,但仍与在某种环境中运行有关。

通过这种方法,您可以从逻辑上依赖于这样的UI线程:

UI代码=>后台线程逻辑=> UI线程

相反,请使用后台线程逻辑中的IProgress<T>Progress<T>将进度报告传递回其调用方,该调用方决定如何显示这些进度报告。然后您的依赖性如下所示:

UI代码=>后台线程逻辑

,并且您的后台线程逻辑不依赖于存在的UI线程。这使得它更具可重用性和可测试性。