【问题标题】:how can i force await to continue on the same thread?我如何强制等待在同一个线程上继续?
【发布时间】:2017-08-03 05:21:04
【问题描述】:

await 不保证衍生任务继续执行同一任务:

private void TestButton_Click(object sender, RoutedEventArgs e)
{
    Task.Run(async () =>
    {
        Debug.WriteLine("running on task " + Task.CurrentId);
        await Task.Delay(TimeSpan.FromMilliseconds(100));
        Debug.WriteLine("running on task " + Task.CurrentId);
    });
}

这个的输出是:

running on task 1
running on task

所以我们可以看到不仅执行已经转移到另一个任务,而且还转移到了 UI 线程。我怎样才能创建一个专门的任务,并强制等待总是继续这个任务?长时间运行的任务也不会这样做。

我见过几个SynchronizationContext implementations,但到目前为止都没有工作,在这种情况下,因为它使用线程并且 System.Threading.Thread 不适用于 uwp。

【问题讨论】:

  • 您似乎在这里混淆了线程和任务的概念 - 任务意味着更高、更“逻辑”的级别。没有“继续从事同一任务”的真正概念。
  • "...same thread..." - 如果您确实必须为给定任务转储当前线程,请使用Environment.CurrentManagedThreadId。尽管在您的示例中它们将是 same
  • @Benni:如果您同时从多个线程调用非线程安全 API,则只需要“大规模锁定”。除非 API 仅限于从单个线程调用,否则您从哪个线程调用无关紧要。如果 API 仅限于从单个线程调用,那么您需要仔细控制该线程,并且使用 async 和 await 可能是个坏主意。此外,您似乎将线程与任务混淆了。如果您关心用于调用 API 的线程,那么您不应该关心 Task.CurrentId
  • 线程是工人。在这个类比中,从来没有雇佣过第二个工人。但任务只是待办事项清单上的项目;是否分配给几个工人,以及这些工人如何合作决定他们如何分配,并不能改变一个工人的名字和任务的位置之间存在很大差异的事实。 -做清单。
  • 我也很不清楚您为什么在事件处理程序中使用 Task.Run,​​以及为什么不等待返回的任务。您编写的代码和您提出的问题都很奇怪。我怀疑你有我们所说的“XY 问题”。您对如何解决问题有一些错误的想法,您是在向我们询问错误的解决方案,而不是寻求帮助解决真正的问题。使用 async-await 的预期用途:构建任务工作流。

标签: c# asynchronous task-parallel-library synchronizationcontext


【解决方案1】:

不要使用Task.Run,只需让事件处理程序异步

 private async void TestButton_Click(object sender, RoutedEventArgs e)
 {
     await dedicated();
 }

 private async Task dedicated()
 {
     Console.WriteLine("running on task {0}", Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null");
     await Task.Delay(TimeSpan.FromMilliseconds(100));
     Console.WriteLine("running on task {0}", Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null");
 }

在异步方法here 中阅读有关Task.CurrentId 的更多信息:

所以Task.CurrentId 返回null 因为实际上没有任务 正在执行。

回复cmets

  1. 它仍然在 UI 线程上运行,而不是衍生任务。

带有线程池线程的案例包含在链接中。特别看这个例子

static void Main(string[] args)
{
    var task = Task.Run(() => MainAsync());
    task.Wait();
    taskRun = task.Id.ToString();

    Console.WriteLine(beforeYield + "," + afterYield + "," + taskRun);
    Console.ReadKey();
}

static async Task MainAsync()
{
    beforeYield = Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null";
    await Task.Yield();
    afterYield = Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null";
}

再次澄清

null 发挥作用是因为 async 方法首先被执行为 线程池上的实际任务。但是,在它的await 之后,它 恢复为线程池上的常规委托(不是实际任务)。

  1. 我的问题是如何阻止它这样做

这是async调用的一个实现细节,我只能再次引用链接:

这种行为很可能只是最简单的结果 最有效的实施。

所以你不能也不应该阻止它这样做,就真正async电话而言。

您所描述的预期行为相当于Task.Run 没有 await

 private void expected()
 {
     Task task = Task.Run(() =>
     {
         Console.WriteLine("Before - running on task {0} {1}", 
            Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null",
            Environment.CurrentManagedThreadId);
         Task.Delay(TimeSpan.FromMilliseconds(100)).Wait();
         Console.WriteLine("After - running on task {0} {1}", 
            Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null",
            Environment.CurrentManagedThreadId);
     });
 }

或者嵌套的Task.Run

 private void expected()
 {
     Task task = Task.Run(() =>
     {
         Console.WriteLine("Before - running on task {0} {1}",
             Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null",
             Environment.CurrentManagedThreadId);
         var inner = Task.Run( async () =>
             await Task.Delay(TimeSpan.FromMilliseconds(100)));
         inner.Wait();
         Console.WriteLine("After - running on task {0} {1}",
             Task.CurrentId.HasValue ? Task.CurrentId.ToString() : "null",
             Environment.CurrentManagedThreadId);
     });
 }

输出

Before - running on task 312 11
After - running on task 312 11
Before - running on task 360 11
After - running on task 360 11
Before - running on task 403 15
After - running on task 403 15

【讨论】:

  • 这将使它在 UI 线程上运行,而不是在专用线程上。
  • 我知道它在线程池中恢复,我的问题是如何停止它这样做。
【解决方案2】:

所以我们可以看到不仅执行已经转移到另一个任务,而且还转移到了 UI 线程。

不,它不在 UI 线程上。从技术上讲,这也不是一项任务。我在 Task.CurrentId in async methods 上的博客文章中解释了为什么会发生这种情况。

我怎样才能创建一个专门的任务,并强制等待总是继续这个任务?长时间运行的任务也不会这样做。

您走在正确的轨道上:您需要自定义 SynchronizationContext(或自定义 TaskScheduler)。

我见过几个 SynchronizationContext 实现,但到目前为止它们都没有工作,在这种情况下,因为它使用线程并且 System.Threading.Thread 不适用于 uwp。

试试mine。它应该适用于 UWP 10.0。

【讨论】:

  • 谢谢,我会试一试!有时间也会看源码的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-19
  • 2010-09-05
  • 1970-01-01
  • 1970-01-01
  • 2018-05-28
  • 1970-01-01
相关资源
最近更新 更多