C#“异步并等待”功能和线程 - c#

请参阅以下代码。

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

参考方案

原始答案

我假设Main()是在主线程(例如Thread1)上调用的。

是的,Main将在主线程上运行。

调用AddAsync(),但是此方法带有异步标记,因此可能在辅助线程(例如Thread2)上调用此方法。但是AddAsync()方法中以下代码的结果表明Thread1与我期望的Thread2不同:

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!");
}

调用Sum()方法。但是通过使用await关键字进行修饰,将在辅助线程(例如Thread3)上调用Sum()方法。

它可能会或可能不会在同一线程中运行。 Sum很可能会在主线程中运行,因为当主线程正在等待Sum时,我们需要一个线程来运行Sum,因此主线程可用。

如果在Sum的开头添加以下行,我希望它说出与主线程相同的id:

Console.WriteLine("ID of thread in Sum(): {0}", Thread.CurrentThread.ManagedThreadId);

在Sum()方法内部,在辅助线程(例如Thread4)上调用Run()方法。

是的,Task.Run将使用另一个线程,默认情况下是ThreadPool中的一个。注意:我默认说,因为这取决于TaskScheduler,默认情况下将使用ThreadPool

我现在这样理解:当我进行异步编程时,创建新线程取决于CLR。 CLR尽可能创建多线程,但是CLR也可以在单个线程上处理异步任务,只需在单个线程上同时异步处理多个任务即可。我只是假设一个简单的情况,每次尝试异步任务时,CLR都会创建一个新线程。

它不会每次都启动新线程。 async/await不是Thread周围的语法糖,而是Task和延续处。 Task已被设计为避免使用不必要的新线程(例如a Task may run inline)或使用ThreadPool

AddAsync()方法中的以下代码是否真的指示“ Main()”中线程的ID?对我来说,使用“ AddAsync()中的线程ID”会更合适。

Console.WriteLine("ID of thread in Main(): {0}",  Thread.CurrentThread.ManagedThreadId);

如上面代码中的注释所示,是的,这是主线程。

达到await Task.Run...后,主线程将处于空闲状态,因为它必须等待任务完成。恢复时,它将返回到AddAsync,运行Console.WriteLine("Other thread is done!");,然后返回到运行MainConsole.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称为诺言。另一个示例是从文件读取,例如:

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用于承诺任务,它将使它保持对输入事件的响应。这样可以节省您使用BackgroundWorker的时间...好吧,除非您需要一些需要大量CPU时间的内容,否则您将需要使用Task.Run,调用ThreadPool,使用BackgroundWorker或启动Thread

你的问题

因此,我可以在这段代码中说,仅Task.Run()创建一个新线程吗?和async和await关键字不会创建新线程吗?

不,Task.Run正在使用另一个线程,但未创建它。默认情况下,它退回到ThreadPool

ThreadPool是做什么的?好吧,它保留了一小组线程,可用于按需运行操作(例如,运行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();
}

此时,我们将创建一个不完整的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返回的内容。您没有Waitawait或向其添加延续性...第二个线程没有别的事情要做,现在第二个线程消逝了。

注意:请注意,第二个线程来自ThreadPool。您可以阅读Thread.CurrentThread.IsThreadPoolThread检查自己。

当回复有时是一个对象有时是一个数组时,如何在使用改造时解析JSON回复? - java

我正在使用Retrofit来获取JSON答复。这是我实施的一部分-@GET("/api/report/list") Observable<Bills> listBill(@Query("employee_id") String employeeID); 而条例草案类是-public static class…

改造正在返回一个空的响应主体 - java

我正在尝试使用Retrofit和Gson解析一些JSON。但是,我得到的响应机构是空的。当我尝试从对象中打印信息时,出现NullPointerException。我确保URL正确,并且我也确保POJO也正确。我正在使用jsonschema2pojo来帮助创建POJO类。这是我要解析的JSON{ "?xml": { "@versi…

每个文件合并后添加换行 - python

我有很多类似以下内容的JSON文件:例如。1.json{"name": "one", "description": "testDescription...", "comment": ""} test.json{"name"…

Json到php,json_decode返回NULL - php

我正在用PHP进行JSON解析器的一些API,用于存储有关遗产的信息。我在解析时遇到问题,因为它返回的是NULL值而不是数组或对象。简单的JSON代码可以很好地解析,但是可以这样:{"success":true,"totalCount":1,"data":[{"id":99694…

C#中的开关/案例情况下循环-如何? - c#

我正在尝试使用c#扩展给定的代码,但是由于缺乏编程经验,我有点陷入困境。使用Visual Studio社区,我试图通过控制台读取CPU核心温度。该代码使用开关/外壳来查找传感器特定名称的外壳,例如CPU Core#1和温度。对于每个CPU内核。拥有64个核心,这是相当不错的选择。我试图实现一个foreach情况,但是它不起作用。我也不知道如何在这里设置for…