我认为它们基本上是同一件事——编写在处理器之间分配任务的程序(在有2个以上处理器的机器上)。然后我读到这个,上面写着

Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.

我想知道是否有人能帮我翻译成英文。它似乎在异步性(有这个词吗?)和线程之间划出了界限,并暗示您可以有一个具有异步任务但没有多线程的程序。

现在我理解了异步任务的概念,比如Jon Skeet的c#深度第三版第467页的例子

async void DisplayWebsiteLength ( object sender, EventArgs e )
{
    label.Text = "Fetching ...";
    using ( HttpClient client = new HttpClient() )
    {
        Task<string> task = client.GetStringAsync("http://csharpindepth.com");
        string text = await task;
        label.Text = text.Length.ToString();
    }
}

async关键字的意思是“无论何时调用该函数,在调用后的所有内容都需要补全的上下文中都不会调用该函数。”

换句话说,就是在某个任务中编写它

int x = 5; 
DisplayWebsiteLength();
double y = Math.Pow((double)x,2000.0);

,因为DisplayWebsiteLength()与x或y无关,将导致DisplayWebsiteLength()被“在后台”执行,如

                processor 1                |      processor 2
-------------------------------------------------------------------
int x = 5;                                 |  DisplayWebsiteLength()
double y = Math.Pow((double)x,2000.0);     |

显然这是一个愚蠢的例子,但我是正确的还是我完全糊涂了?

(另外,我对为什么sender和e没有在上面的函数体中使用感到困惑。)


你的误解非常普遍。许多人被教导多线程和异步是一回事,但事实并非如此。

打个比方通常会有帮助。你在餐厅做饭。一份鸡蛋和吐司的订单送来了。

Synchronous: you cook the eggs, then you cook the toast. Asynchronous, single threaded: you start the eggs cooking and set a timer. You start the toast cooking, and set a timer. While they are both cooking, you clean the kitchen. When the timers go off you take the eggs off the heat and the toast out of the toaster and serve them. Asynchronous, multithreaded: you hire two more cooks, one to cook eggs and one to cook toast. Now you have the problem of coordinating the cooks so that they do not conflict with each other in the kitchen when sharing resources. And you have to pay them.

Now does it make sense that multithreading is only one kind of asynchrony? Threading is about workers; asynchrony is about tasks. In multithreaded workflows you assign tasks to workers. In asynchronous single-threaded workflows you have a graph of tasks where some tasks depend on the results of others; as each task completes it invokes the code that schedules the next task that can run, given the results of the just-completed task. But you (hopefully) only need one worker to perform all the tasks, not one worker per task.

It will help to realize that many tasks are not processor-bound. For processor-bound tasks it makes sense to hire as many workers (threads) as there are processors, assign one task to each worker, assign one processor to each worker, and have each processor do the job of nothing else but computing the result as quickly as possible. But for tasks that are not waiting on a processor, you don't need to assign a worker at all. You just wait for the message to arrive that the result is available and do something else while you're waiting. When that message arrives then you can schedule the continuation of the completed task as the next thing on your to-do list to check off.

让我们更详细地看看Jon的例子。会发生什么呢?

Someone invokes DisplayWebSiteLength. Who? We don't care. It sets a label, creates a client, and asks the client to fetch something. The client returns an object representing the task of fetching something. That task is in progress. Is it in progress on another thread? Probably not. Read Stephen's article on why there is no thread. Now we await the task. What happens? We check to see if the task has completed between the time we created it and we awaited it. If yes, then we fetch the result and keep running. Let's suppose it has not completed. We sign up the remainder of this method as the continuation of that task and return. Now control has returned to the caller. What does it do? Whatever it wants. Now suppose the task completes. How did it do that? Maybe it was running on another thread, or maybe the caller that we just returned to allowed it to run to completion on the current thread. Regardless, we now have a completed task. The completed task asks the correct thread -- again, likely the only thread -- to run the continuation of the task. Control passes immediately back into the method we just left at the point of the await. Now there is a result available so we can assign text and run the rest of the method.

就像我的类比一样。有人向你要一份文件。你把文件寄出去,然后继续做其他的工作。当邮件到达时,你会收到信号,当你喜欢它的时候,你就完成剩下的工作流程——打开信封,支付运费,等等。你不需要雇佣另一个工人来为你做这些事情。


浏览器内Javascript是异步程序的一个很好的例子,它没有多线程。

You don't have to worry about multiple pieces of code touching the same objects at the same time: each function will finish running before any other javascript is allowed to run on the page. (Update: Since this was written, JavaScript has added async functions and generator functions. These functions do not always run to completion before any other javascript is executed: whenever they reach a yield or await keyword, they yield execution to other javascript, and can continue execution later, similar to C#'s async methods.)

However, when doing something like an AJAX request, no code is running at all, so other javascript can respond to things like click events until that request comes back and invokes the callback associated with it. If one of these other event handlers is still running when the AJAX request gets back, its handler won't be called until they're done. There's only one JavaScript "thread" running, even though it's possible for you to effectively pause the thing you were doing until you have the information you need.

In C# applications, the same thing happens any time you're dealing with UI elements--you're only allowed to interact with UI elements when you're on the UI thread. If the user clicked a button, and you wanted to respond by reading a large file from the disk, an inexperienced programmer might make the mistake of reading the file within the click event handler itself, which would cause the application to "freeze" until the file finished loading because it's not allowed to respond to any more clicking, hovering, or any other UI-related events until that thread is freed.

程序员可能会使用的一种方法是创建一个新线程来加载文件,然后告诉线程的代码,当文件加载时,它需要再次在UI线程上运行剩余的代码,这样它就可以根据在文件中找到的内容更新UI元素。直到最近,这种方法非常流行,因为c#库和语言使它变得简单,但从根本上说,它比它必须要复杂得多。

If you think about what the CPU is doing when it reads a file at the level of the hardware and Operating System, it's basically issuing an instruction to read pieces of data from the disk into memory, and to hit the operating system with an "interrupt" when the read is complete. In other words, reading from disk (or any I/O really) is an inherently asynchronous operation. The concept of a thread waiting for that I/O to complete is an abstraction that the library developers created to make it easier to program against. It's not necessary.

Now, most I/O operations in .NET have a corresponding ...Async() method you can invoke, which returns a Task almost immediately. You can add callbacks to this Task to specify code that you want to have run when the asynchronous operation completes. You can also specify which thread you want that code to run on, and you can provide a token which the asynchronous operation can check from time to time to see if you decided to cancel the asynchronous task, giving it the opportunity to stop its work quickly and gracefully.

在添加async/await关键字之前,c#对如何调用回调代码的理解要明显得多,因为这些回调是以与任务关联的委托的形式出现的。为了仍能让您受益于使用…Async()操作,同时避免代码的复杂性,Async /await将这些委托的创建抽象出来。但它们仍然存在于编译后的代码中。

因此,您可以让UI事件处理程序等待I/O操作,释放UI线程去做其他事情,并且在您完成读取文件后或多或少地自动返回到UI线程——而无需创建新线程。