Regarding this piece of code :
关于这段代码:
static async Task<string> testc()
{
Console.WriteLine("helo async " + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => {
Thread.Sleep(1000);
Console.WriteLine("task " + Thread.CurrentThread.ManagedThreadId);
});
Console.WriteLine("callback "+Thread.CurrentThread.ManagedThreadId);
return "bob";
}
static void Main(string[] args)
{
Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId);
testc();
Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.ReadLine();
}
I get the following output:
我得到以下输出:
helo sync 10
helo async 10
over10
task 11
callback **11**
Which is OK : piece of code after await is executed in the same thread as task itself.
哪个是好的:await之后的代码片段与任务本身在同一个线程中执行。
Now, if I do it in a WPF application :
现在,如果我在WPF应用程序中执行此操作:
private void Button_Click_1(object sender, RoutedEventArgs e)
{
Console.WriteLine("helo sync " + Thread.CurrentThread.ManagedThreadId);
testc();
Console.WriteLine("over" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.ReadLine();
}
It generates following output:
它生成以下输出:
helo sync 8
helo async 8
over8
task 9
callback **8**
Where we can see code after await executed in the UI thread. Well, this is great since it enables manipulating observable collections etc... But I was wondering "Why?" "How could I do the same?" Is this related to some TaskScheduler behavior ? Is this hard-coded in the .NET Framework ?
我们可以在UI线程中执行await后看到代码。嗯,这很棒,因为它可以操纵可观察的集合等...但我想知道“为什么?” “我怎么能这样做?”这与某些TaskScheduler行为有关吗?这是在.NET Framework中进行硬编码的吗?
Thx for any idea you may submit.
感谢您提交的任何想法。
3 个解决方案
#1
7
The reason is that Task.Run will capture the SynchronizationContext
if one is present as it is in a WPF app when starting the task from the UI thread. The Task will then use the SynchronizationContext
to serialize the callback to the UI thread. However, if no context is awailable as in a Console app, the callback will happen on a different thread.
原因是当从UI线程启动任务时,Task.Run将捕获SynchronizationContext,如果它存在于WPF应用程序中。然后,Task将使用SynchronizationContext将回调序列化为UI线程。但是,如果在控制台应用程序中没有上下文可用,则回调将在不同的线程上发生。
Stephen Toub has described this in a blog entry.
Stephen Toub在博客文章中对此进行了描述。
BTW,
be careful when using
never use Thread.Sleep in a Task. It may cause strange behaviour because a task may not be tied to one thread. Use Task.Delay instead.
顺便说一句,使用时请小心,不要在任务中使用Thread.Sleep。它可能会导致奇怪的行为,因为任务可能不会绑定到一个线程。请改用Task.Delay。
#2
2
But I was wondering "Why?"
但我想知道“为什么?”
You've answered that yourself:
你自己回答了这个问题:
Well, this is great since it enables manipulating observable collections etc...
嗯,这很棒,因为它可以操作可观察的集合等...
The whole point of async is to make asynchrony easier to work with - so you can write "synchronous-looking" code which is actually asynchronous. That often includes wanting to stick within one context (e.g. a UI thread) for the whole of the async method - just "pausing" the method (without blocking the UI thread) when you need to await something.
异步的全部意义在于使异步更易于使用 - 因此您可以编写实现异步的“同步查找”代码。这通常包括想要在整个异步方法的一个上下文(例如UI线程)中 - 只需“暂停”该方法(不阻止UI线程),当您需要等待某事时。
"How could I do the same?"
“我怎么能这样做?”
It's not clear what you mean here. Basically, the implementation of the awaitable pattern for Task
uses TaskScheduler.FromCurrentSynchronizationContext()
to work out which scheduler to post the callback on - unless you've called ConfigureAwait(false)
to explicitly opt out of this behaviour. So that's how it manages it... whether or not you could "do the same" depends on exactly what you're trying to do.
目前尚不清楚你的意思。基本上,Task的等待模式的实现使用TaskScheduler.FromCurrentSynchronizationContext()来计算要在哪个调度程序上发布回调 - 除非您调用ConfigureAwait(false)以明确选择退出此行为。这就是它如何管理它......你是否可以“做同样的事情”取决于你正在尝试做什么。
See the "what are awaitables" question in the async/await FAQ for more details of the awaitable pattern.
有关等待模式的更多详细信息,请参阅async / await FAQ中的“等待的内容”问题。
#3
0
You may find my async
/await
intro helpful. The other answers are almost correct.
您可能会发现我的异步/等待介绍很有帮助。其他答案几乎是正确的。
When you await
a Task
that has not yet completed, by default a "context" is captured which is used to resume the method when the Task
completes. This "context" is SynchronizationContext.Current
unless it is null, in which case it is TaskScheduler.Current
.
当您等待尚未完成的任务时,默认情况下会捕获“上下文”,用于在任务完成时恢复该方法。这个“上下文”是SynchronizationContext.Current,除非它是null,在这种情况下它是TaskScheduler.Current。
Note the conditions required for this to work:
请注意此工作所需的条件:
- "When you
await
..." - if you schedule a continuation manually, e.g., withTask.ContinueWith
, no context capture is done. You have to do it yourself using something like(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext())
. - “当你等待......” - 如果你手动安排继续,例如使用Task.ContinueWith,则不会进行上下文捕获。你必须自己使用类似的东西(SynchronizationContext.Current == null?TaskSchedler.Current:TaskScheduler.FromCurrentSynchronizationContext())。
- "...
await
aTask
..." - this behavior is part of theawait
behavior forTask
types. Other types may or may not do a similar capture. - “......等待任务......” - 此行为是任务类型的等待行为的一部分。其他类型可能会也可能不会进行类似的捕获。
- "...that has not yet completed..." - if the
Task
is already complete by the time it'sawait
ed, theasync
method will continue synchronously. So there's no need to capture context in that case. - “......尚未完成...” - 如果任务在等待时已经完成,则异步方法将同步继续。因此,在这种情况下无需捕获上下文。
- "...by default..." - this is the default behavior and can be changed. In particular, call the
Task.ConfigureAwait
method and passfalse
for thecontinueOnCapturedContext
parameter. This method returns an awaitable type (not aTask
) that will not continue on the captured context if its parameter wasfalse
. - “...默认情况下......” - 这是默认行为,可以更改。特别是,调用Task.ConfigureAwait方法并为continueOnCapturedContext参数传递false。此方法返回一个等待类型(不是任务),如果其参数为false,则不会在捕获的上下文中继续。
#1
7
The reason is that Task.Run will capture the SynchronizationContext
if one is present as it is in a WPF app when starting the task from the UI thread. The Task will then use the SynchronizationContext
to serialize the callback to the UI thread. However, if no context is awailable as in a Console app, the callback will happen on a different thread.
原因是当从UI线程启动任务时,Task.Run将捕获SynchronizationContext,如果它存在于WPF应用程序中。然后,Task将使用SynchronizationContext将回调序列化为UI线程。但是,如果在控制台应用程序中没有上下文可用,则回调将在不同的线程上发生。
Stephen Toub has described this in a blog entry.
Stephen Toub在博客文章中对此进行了描述。
BTW,
be careful when using
never use Thread.Sleep in a Task. It may cause strange behaviour because a task may not be tied to one thread. Use Task.Delay instead.
顺便说一句,使用时请小心,不要在任务中使用Thread.Sleep。它可能会导致奇怪的行为,因为任务可能不会绑定到一个线程。请改用Task.Delay。
#2
2
But I was wondering "Why?"
但我想知道“为什么?”
You've answered that yourself:
你自己回答了这个问题:
Well, this is great since it enables manipulating observable collections etc...
嗯,这很棒,因为它可以操作可观察的集合等...
The whole point of async is to make asynchrony easier to work with - so you can write "synchronous-looking" code which is actually asynchronous. That often includes wanting to stick within one context (e.g. a UI thread) for the whole of the async method - just "pausing" the method (without blocking the UI thread) when you need to await something.
异步的全部意义在于使异步更易于使用 - 因此您可以编写实现异步的“同步查找”代码。这通常包括想要在整个异步方法的一个上下文(例如UI线程)中 - 只需“暂停”该方法(不阻止UI线程),当您需要等待某事时。
"How could I do the same?"
“我怎么能这样做?”
It's not clear what you mean here. Basically, the implementation of the awaitable pattern for Task
uses TaskScheduler.FromCurrentSynchronizationContext()
to work out which scheduler to post the callback on - unless you've called ConfigureAwait(false)
to explicitly opt out of this behaviour. So that's how it manages it... whether or not you could "do the same" depends on exactly what you're trying to do.
目前尚不清楚你的意思。基本上,Task的等待模式的实现使用TaskScheduler.FromCurrentSynchronizationContext()来计算要在哪个调度程序上发布回调 - 除非您调用ConfigureAwait(false)以明确选择退出此行为。这就是它如何管理它......你是否可以“做同样的事情”取决于你正在尝试做什么。
See the "what are awaitables" question in the async/await FAQ for more details of the awaitable pattern.
有关等待模式的更多详细信息,请参阅async / await FAQ中的“等待的内容”问题。
#3
0
You may find my async
/await
intro helpful. The other answers are almost correct.
您可能会发现我的异步/等待介绍很有帮助。其他答案几乎是正确的。
When you await
a Task
that has not yet completed, by default a "context" is captured which is used to resume the method when the Task
completes. This "context" is SynchronizationContext.Current
unless it is null, in which case it is TaskScheduler.Current
.
当您等待尚未完成的任务时,默认情况下会捕获“上下文”,用于在任务完成时恢复该方法。这个“上下文”是SynchronizationContext.Current,除非它是null,在这种情况下它是TaskScheduler.Current。
Note the conditions required for this to work:
请注意此工作所需的条件:
- "When you
await
..." - if you schedule a continuation manually, e.g., withTask.ContinueWith
, no context capture is done. You have to do it yourself using something like(SynchronizationContext.Current == null ? TaskSchedler.Current : TaskScheduler.FromCurrentSynchronizationContext())
. - “当你等待......” - 如果你手动安排继续,例如使用Task.ContinueWith,则不会进行上下文捕获。你必须自己使用类似的东西(SynchronizationContext.Current == null?TaskSchedler.Current:TaskScheduler.FromCurrentSynchronizationContext())。
- "...
await
aTask
..." - this behavior is part of theawait
behavior forTask
types. Other types may or may not do a similar capture. - “......等待任务......” - 此行为是任务类型的等待行为的一部分。其他类型可能会也可能不会进行类似的捕获。
- "...that has not yet completed..." - if the
Task
is already complete by the time it'sawait
ed, theasync
method will continue synchronously. So there's no need to capture context in that case. - “......尚未完成...” - 如果任务在等待时已经完成,则异步方法将同步继续。因此,在这种情况下无需捕获上下文。
- "...by default..." - this is the default behavior and can be changed. In particular, call the
Task.ConfigureAwait
method and passfalse
for thecontinueOnCapturedContext
parameter. This method returns an awaitable type (not aTask
) that will not continue on the captured context if its parameter wasfalse
. - “...默认情况下......” - 这是默认行为,可以更改。特别是,调用Task.ConfigureAwait方法并为continueOnCapturedContext参数传递false。此方法返回一个等待类型(不是任务),如果其参数为false,则不会在捕获的上下文中继续。