You*_*ark 5 c# multithreading asynchronous
请查看以下代码。
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);
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
结果是这样的:
***** Adding with Thread objects *****
ID of thread in Main(): 1
ID of thread in Add(): 3
10 + 10 is 20
Other thread is done!
Run Code Online (Sandbox Code Playgroud)
我得到的结果与我预期的不同。请通过给出正确的概念来纠正我的假设。
1. 我假设 Main() 是在主线程上调用的,比如说 Thread1。
2. 调用 AddAsync() 但这个方法被标记为 async,所以这个方法可能是在辅助线程上调用的,比如 Thread2。但是 AddAsync() 方法中的以下代码的结果表明 Thread1 与我预期的 Thread2 不同:
Console.WriteLine("ID of thread in Main(): {0}",Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)
3. 调用 Sum() 方法。但是通过 await 关键字修饰,Sum() 方法在辅助线程上调用,比如 Thread3。
我此时的理解是这样的:当我进行异步编程时,创建新线程依赖于 CLR。CLR 尽可能地创建多线程,但 CLR 也可以在单个线程上处理异步任务,只需在单个线程上异步处理多个任务即可。我只是假设一个简单的情况,每次我尝试异步任务时,CLR 都会创建一个新线程。
我知道这个话题很难描绘,所以如果能用图来解释会更好。
附加问题。
1. AddAsync() 方法里面的以下代码真的表示“Main()”中的线程ID?对我来说,使用“AddAsync() 中的线程 ID”会更合适。
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)
- 我假设 Main() 在主线程(例如 Thread1)上调用。
是的,Main将在主线程上运行。
- 调用 AddAsync() 但此方法被标记为异步,因此此方法可能在辅助线程(例如 Thread2)上调用。但是 AddAsync() 方法中以下代码的结果表明 Thread1 与我预期的 Thread2 不同:
Console.WriteLine("ID of thread in Main(): {0}",Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)
不会。如果未达到 ,代码将不会切换线程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!");
}
Run Code Online (Sandbox Code Playgroud)
- 调用 Sum() 方法。但是通过await关键字修饰,Sum()方法在辅助线程(例如Thread3)上调用。
它可能会也可能不会在同一个线程中运行。它很可能Sum会在主线程中运行,因为当主线程正在等待Sum并且我们需要一个线程来运行时,Sum主线程可用。
如果您在 的开头添加以下行Sum,我希望它显示与主线程相同的 id:
Console.WriteLine("ID of thread in Sum(): {0}", Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)
- 在 Sum() 方法内部,在辅助线程(例如 Thread4)上调用 Run() 方法
是的,Task.Run将使用另一个线程,默认情况下来自ThreadPool. 注意:我说默认,因为这取决于TaskScheduler,默认的将使用ThreadPool.
我现在的理解是这样的:当我进行异步编程时,创建新线程取决于CLR。CLR 尽可能创建多线程,但是 CLR 也可以在单个线程上处理异步任务,只需在单个线程上同时异步处理多个任务即可。我只是假设简单的情况,每次尝试异步任务时,CLR 都会创建一个新线程。
它不会每次都启动新线程。async/await不是 around 的语法糖Thread,而是 aroundTask和 延续。Task已经设计为避免在不必要的情况下使用新线程,例如aTask可以内联运行,或者使用ThreadPool.
- AddAsync() 方法中的以下代码真的表示“Main()”中线程的 ID 吗?对我来说,“AddAsync() 中的线程 ID”更合适。
Console.WriteLine("ID of thread in Main(): {0}", Thread.CurrentThread.ManagedThreadId);
Run Code Online (Sandbox Code Playgroud)
正如上面代码中的注释所示,是的,这就是主线程。
到达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);
Run Code Online (Sandbox Code Playgroud)
如您所见,您的代码不需要并行性。除了使用它之外 勘误表:经过进一步检查,存在并行性,只是不那么明显......请参阅扩展答案。Task.Run,它还可以在单线程中运行。
经过第二次阅读后,我怀疑您期望该调用AddAsync并行运行。正如我上面所说,您没有等待它,在这种情况下,它像常规同步调用一样运行。
如果你想AddAsync并行运行,我建议使用Task.Run,例如:
Task.Run((Func<Task>)AddAsync);
Run Code Online (Sandbox Code Playgroud)
这样做,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();
// ...
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,读取文件的行为将不需要您的线程之一。在内部,操作系统会要求驱动程序将数据复制到 RAM 缓冲区,并在完成时发出通知(这在现代硬件中会发生,因为DMA需要 CPU 的干预最少),因此那里不使用任何线程。
同时,调用线程可以自由地执行其他操作。如果您有多个类似的操作(例如,您可能正在从文件中读取、将数据发送到网络等),它们可以并行发生,而不使用您的线程,当其中一个操作完成时,您的执行代码将在您的可用线程之一中恢复。
另一件需要考虑的事情是,如果您在 UI 线程上工作,事情的工作方式会略有不同。
在窗口中,很多操作都是从消息队列开始的。无需担心它是如何工作的,但足以说明主线程将花费大量时间等待输入事件(单击、按键等)。
正如您将在扩展答案的末尾看到的那样,方法有可能会在不同的线程中继续。但是,UI 线程只是一个可以与 UI 交互的线程。因此,让 UI 代码在不同的线程中运行并不好。
为了解决这个问题,在UI线程中将await让该线程继续在消息队列上工作。此外,它还会将消息发布到队列中以供继续,从而允许 UI 线程拾取它们。存档的方式是使用不同的TaskSchedulerUI 线程。
这也意味着,如果您在 UI 环境中,并且用于await承诺任务,它将允许它保持对输入事件的响应。这可能会节省你的使用BackgroundWorker......好吧,除非你需要一些需要大量CPU时间的东西,那么你将需要使用Task.Run,调用ThreadPool,使用BackgroundWorker或启动Thread。
你的问题
那么,我可以说在这段代码中,一个新线程仅由 Task.Run() 创建吗?async 和await 关键字不会创建新线程?
不,Task.Run正在使用另一个线程,但没有创建它。默认情况下它会回落到ThreadPool.
其作用是什么ThreadPool?嗯,它保留了一小组线程,可用于按需运行操作(例如运行 a Task),一旦操作完成,线程将返回到ThreadPool保持空闲状态的位置,直到您再次需要它。摘要:ThreadPool回收线程。
在 AddAsync() 内部调用“await Sum(ap)”时,主线程仍在调用 Sum(ap),对吧
是的,它仍然是主线程。下面将对此进行更详细的介绍。
然后转到 Sum() 方法,代码仍在主线程上处理,突然被 Task.Run() 遇到。此时“Task.Run()”创建了一个新线程并在新线程上执行lambda表达式代码?
正如我上面所说,Task.Run如果不需要,不会创建新线程。它将要求ThreadPool在其线程之一上运行该操作。这些ThreadPool线程用于运行一次性操作,因此您最终不会创建大量线程Thread,而只会回收一些线程。
所以,是的,lambda 中的代码将在不同的 中运行Thread,但它并不是专门为此创建的。
当“Task.Run()”正在处理时,等待Task.Run()方法结果的调用线程(主线程)的状态是什么?是被屏蔽还是不被屏蔽?
首先请注意,您有两个等待选项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();
}
Run Code Online (Sandbox Code Playgroud)
此时,我们将创建一个不完整的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!");
}
Run Code Online (Sandbox Code Playgroud)
让我稍微重构一下,很快......
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!");
}
Run Code Online (Sandbox Code Playgroud)
接下来发生的事情是调用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);
}
});
}
Run Code Online (Sandbox Code Playgroud)
还有更多恶作剧......让我重构该代码......
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;
}
Run Code Online (Sandbox Code Playgroud)
上面的代码与我们的代码等效。请注意,您可以使用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;
}
Run Code Online (Sandbox Code Playgroud)
现在,有趣的部分......
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
}
Run Code Online (Sandbox Code Playgroud)
现在该方法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!");
}
Run Code Online (Sandbox Code Playgroud)
现在该方法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
}
Run Code Online (Sandbox Code Playgroud)
与此同时,第二个线程完成......
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;
}
Run Code Online (Sandbox Code Playgroud)
并触发我们添加到的延续x。
该延续将 Sum Task ( 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
}
Run Code Online (Sandbox Code Playgroud)
现在,AddAsync结束了。然而,正如我上面所说,你只是忽略了AddAsync返回的内容。您没有Wait或 或await向其添加延续...第二个线程没有其他事情可做,现在第二个线程死亡。
注意:需要明确的是,第二个线程来自ThreadPool. 您可以通过阅读来检查自己Thread.CurrentThread.IsThreadPoolThread。
| 归档时间: |
|
| 查看次数: |
5009 次 |
| 最近记录: |