Async await and threads [duplicate] (3个答案)
3年前关闭。
请参阅以下代码。
class AddParams
{
public int a, b;
public AddParams(int numb1, int numb2)
{
a = numb1;
b = numb2;
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("ID of thread in 1: {0}",
Thread.CurrentThread.ManagedThreadId);
AddAsync();
Console.ReadLine();
}
private static async Task AddAsync()
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10);
await Sum(ap);
Console.WriteLine("Other thread is done!");
}
static async Task Sum(object data)
{
await Task.Run(() =>
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
});
}
}
结果是这样的:
***** Adding with Thread objects *****
ID of thread in Main(): 1
ID of thread in Add(): 3
10 + 10 is 20
Other thread is done!
我得到的结果与我的预期不同。
请通过给出正确的概念来纠正我的假设。
1。
我假设Main()是在主线程(例如Thread1)上调用的。
2。
调用AddAsync(),但是此方法带有异步标记,因此可能在辅助线程(例如Thread2)上调用此方法。
但是AddAsync()方法中以下代码的结果表示Thread1与我期望的Thread2不同:
Console.WriteLine("ID of thread in Main(): {0}",Thread.CurrentThread.ManagedThreadId);
3。
调用Sum()方法。但是通过使用await关键字进行修饰,将在辅助线程(例如Thread3)上调用Sum()方法。
在Sum()方法内部,在辅助线程(例如Thread4)上调用Run()方法。 我目前正在这样理解:
当我进行异步编程时,创建新线程取决于CLR。 CLR尽可能创建多线程,但是CLR也可以在单个线程上处理异步任务,只需在单个线程上同时异步处理多个任务即可。
我只是假设一个简单的情况,每次尝试异步任务时,CLR都会创建一个新线程。
我知道这个主题很难描述,因此如果用图纸进行解释会更好。
其他问题。
1。
AddAsync()方法中的以下代码是否真的指示“Main()”中线程的ID?对我来说,使用“AddAsync()中的线程ID”会更合适。
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
原始答案
- I assume that Main() is invoked on the primary thread, say Thread1.
正确,
Main
将在主线程上运行。
- Invoke AddAsync() but this method is marked with async, so this method is possibly invoked on a secondary thread, say Thread2. But the result of the following code inside of AddAsync() method says Thread1 unlike Thread2 which I expected:
Console.WriteLine("ID of thread in Main(): {0}",Thread.CurrentThread.ManagedThreadId);
否。如果您尚未达到
await
,那么该代码将不会切换线程。
static void Main(string[] args) // <- main thread
{
Console.WriteLine("ID of thread in 1: {0}",
Thread.CurrentThread.ManagedThreadId); // <- main thread
AddAsync(); // <- main thread (note: you are not awaiting here)
Console.ReadLine();
}
private static async Task AddAsync()
{
Console.WriteLine("***** Adding with Thread objects *****"); // <- main thread
Console.WriteLine("ID of thread in Main(): {0}", // <- main thread
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10); // <- main thread
await Sum(ap); // <- ok, we cannot continue.
// Add `Sum(ap)` to pending stuff.
// When Sum(ap) is done we resume here, potentially in another thread.
// The main thread is now free to do pending stuff.
// Turns out `Sum(ap)` is pending, run it on the main thread.
Console.WriteLine("Other thread is done!");
}
- Invoke Sum() method. but by decoration by await keyword, Sum() method is invoked on a secondary thread, say Thread3.
它可能会或可能不会在同一线程中运行。
Sum
很可能会在主线程中运行,因为当主线程正在等待
Sum
时,我们需要一个线程来运行
Sum
,因此主线程可用。
如果您在
Sum
的开头添加以下行,我希望它说出与主线程相同的id:
Console.WriteLine("ID of thread in Sum(): {0}", Thread.CurrentThread.ManagedThreadId);
- Inside of Sum() method, Run() method is invoked on the secondary thread, say Thread4
正确,
Task.Run
将使用另一个线程,默认情况下是
ThreadPool
中的一个。
注意:我说默认情况下,因为这取决于
TaskScheduler
,默认情况下将使用
ThreadPool
。
I'm understanding at this moment like this: When I do asynchronous programming, creating new threads depend on the CLR. CLR creates multi-threads as it could as possible, but CLR also can process asynchronous tasks on a single thread, just by processing multi tasks at the same time asynchronously on a single thread. I just assume simple case that every time I attempt asynchronous tasks, CLR creates a new thread.
它不会每次都启动新线程。
async/await
不是
Thread
周围的语法糖,而是
Task
和延续处。
Task
已被设计为避免使用不必要的新线程,例如
a Task
may run inline或使用
ThreadPool
。
- The following code inside of AddAsync() method really indicates ID of thread in "Main()"? For me, it would be more appropriate with "ID of thread in AddAsync()".
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
如上面代码中的注释所示,是的,这是主线程。
到达
await Task.Run...
后,主线程将处于空闲状态,因为它必须等待任务完成。恢复后,它将返回
AddAsync
,运行
Console.WriteLine("Other thread is done!");
,然后返回到
Main
,在其中运行
Console.ReadLine();
。如果在
Main
调用之前在
Console.ReadLine
中添加以下行,则会看到主线程的ID:
Console.WriteLine("ID of thread before ReadLine: {0}",
Thread.CurrentThread.ManagedThreadId);
如您所见,您的代码不需要并行处理。除了使用
Task.Run
之外,它还可以在单个线程中运行。
勘误表:经过进一步检查,发现存在并行性,只是不那么明显。请参阅扩展的答案。
扩展答案
经过二读后,我怀疑您期望对
AddAsync
的调用并行运行。就像我在上面说的那样,您并没有等待它,在这种情况下,它像常规的同步调用一样运行。
如果要并行运行
AddAsync
,我建议使用
Task.Run
,例如:
Task.Run((Func<Task>)AddAsync);
这样做,
AddAsync
将不再在主线程中运行。主线程将前进到
Console.ReadLine
,甚至可能在
AddASync
之前结束。请注意,执行将在主线程结束后立即结束。
当然,
AddAsync
很快,我建议给
await
一些
Task.Delay
给你一些时间来敲那个键。
在您提出问题之前,让我发布问题:
Task.Delay
如何工作? -内部结构的简化解释(至少在Windows上是),它将向操作系统询问超时。当操作系统看到时间已到时,它将调用程序以通知超时已完成。这样
Task.Delay
不需要使用线程来运行。
那是
Task
的另一种类型,它不必运行代码,因此不需要占用线程。我们可以将这类Taks称为Promise。另一个示例是从文件读取,例如:
using (var reader = File.OpenText("example.txt"))
{
var fileText = await reader.ReadToEndAsync();
// ...
}
在这种情况下,读取文件的操作将不需要您的线程之一。在内部,操作系统将要求驱动程序将数据复制到RAM缓冲区中,并在完成时通知(由于
DMA需要最少的CPU干预,因此在现代硬件中会发生这种情况),因此该处不使用任何线程。
同时,调用线程可以自由地做其他事情。如果您有这样的多种操作(例如,您可能正在从文件中读取数据,将数据发送到网络等),则它们可以并行发生,而无需使用线程,并且当其中一个线程完成时,您的线程便会执行。代码将在您可用的线程之一中恢复。
还要考虑的另一件事是,如果您正在使用UI线程,则工作原理完全不同。
在窗口中,许多操作都从
message queue开始。无需担心其工作原理,只需要说主线程将花费大量时间等待输入事件(单击,按键等)。
正如您将在扩展答案的结尾看到的那样,方法可能会在其他线程中继续执行。但是,UI线程仅仅是可以与UI进行交互的线程。因此,让UI代码在不同的线程中运行是不好的。
要解决此问题,请在UI线程中使用
await
使该线程继续在消息队列上工作。此外,它将消息继续发送到队列中,以允许UI线程接收消息。存档的方法是为UI线程使用不同的
TaskScheduler
。
这也意味着,如果您处于UI环境中,并且将
await
用于 promise 任务,它将使它保持对输入事件的响应。这样可以节省您使用
BackgroundWorker
的开销。嗯,除非您需要一些需要大量CPU时间的东西,否则您将需要使用
Task.Run
,调用
ThreadPool
,使用
BackgroundWorker
或启动
Thread
。
您的问题
So, can I say in this code, a new thread is created only by Task.Run()? and async and await keyword don't create a new thread?
不,
Task.Run
正在使用另一个线程,但是没有创建它。默认情况下,它回退到
ThreadPool
。
ThreadPool
是做什么的? 好吧,它保留了一小组线程,可用于按需运行操作(例如,运行
Task
),一旦操作完成,线程将返回到
ThreadPool
,它将保持空闲状态直到再次需要它为止。对于摘要:
ThreadPool
回收线程。
At this invoking point "await Sum(ap)" inside of AddAsync(), main thread is still invoking Sum(ap), right
是的,它仍然是主线程。将在下面对此进行更详细的介绍。
And go to Sum() method, code still being processed on the main thread, suddenly encounted by Task.Run(). At this point of "Task.Run()", a new thread is created and lambda expression code is executed on the new thread?
就像我在上面说的,
Task.Run
不必创建新线程。它将要求
ThreadPool
在其线程之一上运行该操作。这些
ThreadPool
线程可以在其中运行一次,因此您不必最终创建许多
Thread
,而只能回收其中的一些。
因此,是的,lambda中的代码将在不同的
Thread
中运行,但并不是为此而创建的。
And when the "Task.Run()" is being processed, what's the state of the calling thread(main thread) awaiting the result of Task.Run() method? Is it being blocked or non-blocked?
首先请注意,您有两个选择要等待
Task.Run
。您可以使用
await
,也可以使用
Task.Wait
。
await
将:
“暂停”该方法的执行。 获取或创建方法的不完整Task
。 向Task
添加一个延续,以“恢复”该方法。或者,如果没有其他可运行的内容,则继续操作会将不完整的Task
设置为完成。 向调用者返回不完整的Task
。 Task.Wait
将:
阻塞线程,直到完成Task
。 现在,我将更加缓慢地检查代码...
首先,再次同步部分:
static void Main(string[] args) // <-- entry point, main thread
{
Console.WriteLine("ID of thread in 1: {0}",
Thread.CurrentThread.ManagedThreadId); // <-- main thread
AddAsync(); // <-- main thread. You are not awaiting, this is a sync call.
Console.ReadLine();
}
此时,我们将创建一个不完整的
Task
,我将其称为
AddAsync Task
。这就是
AddAsync
返回的内容(不是您将使用它,而是忽略它)。
然后主线程输入
AddAsync
:
private static async Task AddAsync() // <-- called from `AddAsync()`
{
Console.WriteLine("***** Adding with Thread objects *****"); // <-- main thread
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId); // <-- main thread
AddParams ap = new AddParams(10, 10); // <-- main thread
await Sum(ap); // <-- shenanigans!!!
Console.WriteLine("Other thread is done!");
}
让我重构一下,真正的快速...
private static async Task AddAsync() // <-- called from `AddAsync()`
{
Console.WriteLine("***** Adding with Thread objects *****"); // <-- main thread
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId); // <-- main thread
AddParams ap = new AddParams(10, 10); // <-- main thread
var z = Sum(ap); // <-- shenanigans!!!
await z;
Console.WriteLine("Other thread is done!");
}
接下来发生的是对
Sum
的调用。此时,将为
Task
创建一个新的不完整
Sum
。我将其称为
Sum Task
。
接下来,主线程输入
Sum
:
static async Task Sum(object data) // <-- called from `await Sum(ap)`
{
await Task.Run(() =>
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
});
}
还有更多的恶作剧...让我重构该代码...
static async Task Sum(object data) // <-- called from `await Sum(ap)`
{
Action y = () =>
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
};
var x = Task.Run(y);
await x;
}
上面的代码等同于我们所拥有的。请注意,您可以使用
x.Wait()
阻止主线程。我们没有那样做...
static async Task Sum(object data) // <-- called from `await Sum(ap)`
{
Action y = () =>
{
if (data is AddParams)
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
}; // <-- Action created in main thread
var x = Task.Run(y); // <-- main threat: create a new Task x with the action y
// start the new Task in a thread from the thread pool
await x;
}
现在,有趣的部分...
static async Task Sum(object data)
{
Action y = () =>
{
if (data is AddParams) // <-- second thread
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = (AddParams)data;
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b);
}
};
var x = Task.Run(y);
await x; // <-- Add a continuation to x
// so that when it finished, it will set the Sum Task to completed
}
现在方法
Sum
返回(不完整的
Sum Task
)
private static async Task AddAsync()
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10);
var z = Sum(ap); // <-- main thread, z is now the incomplete Sum Task
await z; // <-- Add a continuation to z
// so that when it finished, it will resume `AddAsync`
// `AddAsync` is "paused" now.
// main thread returns the incomplete Async Task
Console.WriteLine("Other thread is done!");
}
现在,方法
AddAsync
返回(不完整的
AddAsync Task
)。我想在这里强调一下:
AddSync
方法尚未完成,但返回的状态不完整。
static void Main(string[] args)
{
Console.WriteLine("ID of thread in 1: {0}",
Thread.CurrentThread.ManagedThreadId);
AddAsync();
Console.ReadLine(); // <-- main thread
}
同时,第二个线程完成...
static async Task Sum(object data)
{
Action y = () =>
{
if (data is AddParams) // <-- second thread
{
Console.WriteLine("ID of thread in Add(): {0}",
Thread.CurrentThread.ManagedThreadId); // <-- second thread
AddParams ap = (AddParams)data; // <-- second thread
Console.WriteLine("{0} + {1} is {2}",
ap.a, ap.b, ap.a + ap.b); // <-- second thread
}
};
var x = Task.Run(y);
await x;
}
并触发我们添加到
x
的延续。
该继续将求和任务(
z
)设置为完成。它将恢复
AddAsync
。
private static async Task AddAsync()
{
Console.WriteLine("***** Adding with Thread objects *****");
Console.WriteLine("ID of thread in Main(): {0}",
Thread.CurrentThread.ManagedThreadId);
AddParams ap = new AddParams(10, 10);
var z = Sum(ap);
await z;
Console.WriteLine("Other thread is done!"); // <-- second thread
}
现在,
AddAsync
完成。但是,正如我上面说的,您只是忽略了
AddAsync
返回的内容。您没有
Wait
,也没有
await
或没有添加延续,...第二个线程没有别的事情要做,现在第二个线程消亡了。
注意:为了清楚起见,第二个线程来自
ThreadPool
。您可以通过阅读
Thread.CurrentThread.IsThreadPoolThread
来检查自己。