并行 异步 多线程_返回(并行)基础知识:不要阻塞线程,让异步I / O为您服务...

并行 异步 多线程

并行 异步 多线程

Stephen Toub is one of my favorite folks at Microsoft. I've asked him questions before, sometimes for myself, sometimes on your behalf, Dear Reader, and I've always received thoughtful and well reasoned answered. Because I believe strongly in Jon Udell's "Conserve Your Keystrokes" philosophy, I always try to get great information out to the community, especially when it's been emailed. Remember, when you slap the keyboard and write an epic email to just a few people, there's millions of people out there that miss out. Email less, blog more. More on this in a moment.

Stephen Toub是我在微软最喜欢的人之一。 亲爱的读者,我曾经问过他一些问题,有时是对我自己,有时是代表您,亲爱的读者,我一直都得到深思熟虑和合理的回答。 因为我坚信乔恩·乌德尔(Jon Udell)的“保存您的击键”哲学,所以我总是尽力将重要信息传达给社区,尤其是通过电子邮件发送时。 请记住,当您击打键盘并给少数人写一封史诗般的电子邮件时,那里有数百万人会错过。 更少发送电子邮件,更多博客。 稍后对此进行更多讨论。

TIP: If you're interested in Patterns for Parallel Programming, run, don't walk, and download the FREE and extensive eBook called, yes, you guessed it, Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4. Yes, that title is long, but it feels shorter if you process it in parallel. Seriously, it's free and there's a C# and Visual Basic version. It's brilliant.
Now, if you're REALLY interested in the topic, go get the book Parallel Programming with Microsoft .NET by Stephen Toub, Ade Miller, Colin Campbell, and Ralph Johnson. The complete book as HTML is also hosted here.

提示:如果您对并行编程模式感兴趣,请运行,不要走路,并下载免费且广泛的电子书,是的,您猜对了, 《并行编程模式:通过.NET Framework理解和应用并行模式》。 4 是的,该标题很长,但是如果并行处理,标题会更短。 认真地说,它是免费的,并且有C#和Visual Basic版本。 这个棒极了。 现在,如果您真的对该主题感兴趣,请阅读Stephen Toub,Ade Miller,Colin Campbell和Ralph Johnson的书《使用Microsoft .NET并行编程》 完整HTML书籍也位于此处

I recently noticed a blog post from my friend Steve Smith where he shares some quick sample code to "Verify a List of URLs in C# Asynchronously." As I know Steve wouldn't mind me digging into this, I did. I started by asking Stephen Toub in the Parallel Computing group at Microsoft.

最近,我注意到朋友史蒂夫·史密斯( Steve Smith)的一篇博客文章,他分享了一些快速的示例代码,以“异步验证C#中的URL列表”。 据我所知,史蒂夫(Steve)不在乎我对此进行深入研究,我做到了。 首先,我询问了Microsoft并行计算小组的Stephen Toub。

Steve Smith wanted to verify a list of URLs for existence. This is the basic synchronous code:

史蒂夫·史密斯(Steve Smith)希望验证存在的URL列表。 这是基本的同步代码:

private static bool RemoteFileExists(string url)
{
try
{
var request = WebRequest.Create(url) as HttpWebRequest;
request.Method = "HEAD";
var response = request.GetResponse() as HttpWebResponse;
return (response.StatusCode == HttpStatusCode.OK);
}
catch
{
return false;
}
}

Then Steve changed the code to be Parallel using the new Parallel features of .NET 4 as Stephen Toub helped me explain in "Back to Parallel Basics" in April.

然后,史蒂夫使用.NET 4的新并行功能将代码更改为并行,因为斯蒂芬·图卜(Stephen Toub)在四月份的“返回并行基础”中帮助我进行了解释。

var linkList = GetLinks();

Action<int> updateLink = i =>
{
UpdateLinkStatus(linkList[i]); //fetch URL and update its status in a shared list
};
Parallel.For(0, linkList.Count, updateLink);

Using Parallel.For is a really great way to introduce some basic naive parallelism into your applications.

使用Parallel.For是将一些基本的天真的并行性引入您的应用程序的绝佳方法。

I'm no expert in parallelism (I've read a great whitepaper...) but I asked Stephen Toub if this was the best and recommended way to solve this problem. Stephen responded from a plane using (his words) "email compiled and tested" examples. With his permission, I've included a derivation of his response here in this blog post for my own, and possibly your, edification.

我不是并行性方面的专家(我读过很棒的白皮书...),但我问斯蒂芬·图布(Stephen Toub)是否是解决此问题的最佳建议方法。 斯蒂芬从飞机上用“用电子邮件发送经过编译和测试”的示例答复。 在他的允许下,我在本博客文章中包括了他的回复,以表达我个人的观点,也可能是出于对你的启发。

From Stephen:

从斯蒂芬:

First, it looked like the author was proposing using a parallel loop to handle this.  That's ok, and certainly easy, but that’s the kind of thing you’d only really want to do in a client application and not a server application.  The issue here is that, while easy, it blocks threads; for a client application, having a few more threads that are blocked typically isn’t a big deal; for a server app, though, if for example you were doing this in response to an incoming ASP.NET or WCF service request, you'd be blocking several threads per request, which will greatly hinder scalability.  Still, to get up and running quickly, and if the extra few threads isn’t problematic, this is a fine way to go. 

首先,看起来作者提议使用并行循环来处理此问题。 没关系,当然也很容易,但是那是您只想在客户端应用程序而不是服务器应用程序中真正要做的事情。 这里的问题是,虽然容易,但它会阻塞线程。 对于客户端应用程序,通常有几个被阻塞的线程并不重要。 但是,对于服务器应用程序,例如,如果您是为了响应传入的ASP.NET或WCF服务请求而执行此操作,则每个请求将阻塞多个线程,这将极大地阻碍可伸缩性。 不过,要快速启动并运行,并且如果多余的线程没有问题,这是一个不错的方法。

Assuming you want you "fan out" quickly and easily and it's OK to block a few threads, you can either use a parallel loop, tasks directly, or Stephen's personal favorite, a PLINQ query, e.g. if I have a function "bool ValidateUrl(string url);", I can use PLINQ to process up to N at a time:

假设您希望快速,轻松地“扇出”并且可以阻塞一些线程,则可以直接使用并行循环,直接执行任务,也可以使用Stephen最喜欢的PLINQ查询,例如,如果我有一个函数“ bool ValidateUrl(字符串url);“,我可以一次使用PLINQ最多处理N个:

bool [] results = (from url in urls.AsParallel() select ValidateUrl(url)).ToArray();

In this example, PLINQ will use up to N threads from the ThreadPool, where N defaults to Environment.ProcessorCount, but you can tack on .WithDegreeOfParallelism(N) after the AsParallel() and provide your own N value.

在此示例中,PLINQ将最多使用ThreadPool中的N个线程,其中N默认为Environment.ProcessorCount,但是您可以在AsParallel()之后附加.WithDegreeOfParallelism(N)并提供您自己的N值。

If Steve was doing this in a console app, which is likely, then as Stephen points out, that's no big deal. You've usually got threads to spare on the client. On the server side, however, you want to avoid blocking threads as much as you can.

如果史蒂夫(Steve)是在控制台应用程序中执行此操作的,那么这很可能,正如史蒂芬(Stephen)指出的,那没什么大不了的。 通常,您可以在客户端上保留一些线程。 但是,在服务器端,您要尽可能避免阻塞线程。

A better solution from a scalability perspective, says Stephen, is to take advantage of asynchronous I/O.  When you ' re calling out across the network, there's no reason (other than convenience) to blocks threads while waiting for the response to come back. Unfortunately, in the past it's been difficult to do this kind of aggregation of async operations.  We' need to rewrite our ValidateUrl method to be something more like:

从可伸缩性的角度来看,更好的解决方案是利用异步I / O。 当你 在网络上呼叫,没有理由(除了方便以外),以块中的线程在等待响应回来。 不幸的是,过去很难进行这种异步操作的聚合。 我们需要重写ValidateUrl方法,使其更像是:

public void ValidateUrlAsync(string url, Action<string,bool> callback);

where the method returns immediately and later calls back through the provided callback to alert whether a given URL is valid or not.  We'd then change our usage of this to be more like this. Notice the use of using System.Collections.Concurrent.ConcurrentQueue representing a thread-safe first in-first out (FIFO) collection, and CountdownEvent, that represents a synchronization primitive that is signaled when its count reaches zero.

该方法立即返回,随后通过提供的回调回调,以警告给定的URL是否有效。 然后,我们将其用法更改为更多此类。 请注意,使用System.Collections.Concurrent.ConcurrentQueue表示一个线程安全的先进先出(FIFO)集合,以及CountdownEvent,它表示一个同步原语,该原语在计数达到零时发出信号。

using(var ce = new CountdownEvent(urls.Length))

{
var results = new ConcurrentQueue<Tuple<string,bool>>();

Action callback = (url,valid) =>
{
results.Enqueue(Tuple.Create(url,valid));
ce.Signal();
};

foreach(var url in urls) ValidateUrlAsync(url, callback);

ce.Wait();
}

Assuming ValidateUrlAsync is written to use async, e.g. (you'd really want the following to do better error-handling, but again, this is email-compiled):

假设ValidateUrlAsync被编写为使用异步,例如(您确实希望以下内容能够更好地处理错误,但是同样,这是电子邮件编译的):

public void ValidateUrlAsync(string url, Action<string,bool> callback)
{
var request = (HttpWebRequest)WebRequest.Create(url);
try
{
request.BeginGetResponse(iar =>
{
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.EndGetResponse(iar);
callback(url, response.StatusCode == HttpStatusCode.OK);
}
catch { callback(url, false); }
finally { if (response != null) response.Close(); }
}, null);
}
catch { callback(url, false); }
}

This example would then this would end up only blocking the main thread launching all of the requests and then blocking waiting for all of the responses, rather than blocking one thread per request.  With a slight change, we could also make the launcher async, for example:

然后,本示例将最终仅阻止主线程启动所有请求,然后阻止等待所有响应,而不是每个请求都阻止一个线程。 稍作更改,我们还可以使启动器异步,例如:

public static void ValidateUrlsAsync(string [] urls, Action<IEnumerable<Tuple<string,bool>> callback)
{
var ce = new CountdownEvent(urls.Length);
var results = new ConcurrentQueue<Tuple<string,bool>>();
Action callback = (url,valid) =>
{
results.Enqueue(Tuple.Create(url,valid));
if (ce.Signal()) callback(results);
};
foreach(var url in urls) ValidateUrlAsync(url, callback);
}

Still, this is all really complicated, and much more difficult than the original one-liner using PLINQ. 

尽管如此,这一切确实非常复杂,比起使用PLINQ的原始单缸纸要困难得多。

This is where Tasks and the new Async CTP come in really handy. Imagine that instead of

这是Tasks和新的Async CTP派上用场的地方。 想象一下,而不是

void ValidateUrlAsync(string url, Action<bool> callback);

void ValidateUrlAsync(string url,Action <bool>回调);

we instead had

相反,我们有

Task<bool> ValidateUrlAsync(string url);

Task <bool> ValidateUrlAsync(string url);

The Task<bool> being returned is much more composable, and represents the result (both the successful completion case and the exceptional case) of the async operation. 

返回的Task <bool>更具可组合性,并且表示异步操作的结果(成功完成情况和例外情况)。

BETA NOTE: It's not possible to have both ASP.NET MVC 3 and the Async CTP installed at the same time. This is a beta conflict thing, it'll be fixed, I'm sure.

注意:不能同时安装ASP.NET MVC 3和Async CTP 我敢肯定,这是一个Beta冲突问题,它将得到解决。

If we had such an operation, and if we had a Task.WhenAll method that took any number of tasks and returned a task to represent them all, then we can easily await all of the results, e.g.

如果我们有这样的操作,并且有一个Task.WhenAll方法接受了任意数量的任务并返回了代表所有任务的任务,那么我们可以轻松地等待所有结果,例如

bool [] results = await Task.WhenAll(from url in urls select ValidateUrlAsync(url));

Nice and simple, entirely asynchronous, no blocked threads, etc. 

简洁漂亮,完全异步,没有阻塞线程等。

(Note that in the Async CTP, Task.WhenAll is currently TaskEx.WhenAll, because since it was an out-of-band CTP we couldn't add the static WhenAll method onto Task like we wanted to.)

(请注意,在异步CTP中,Task.WhenAll当前为TaskEx.WhenAll,因为由于它是带外CTP,所以我们无法像我们想要的那样将静态WhenAll方法添加到Task上。)

With the Async CTP and the await keyword, it's also much easier to implement the ValidateUrlAsync method, and to do so with complete support for exception handling (which I didn't do in my previous example, i.e. if something fails, it doesn't communicate why).

使用Async CTP和await关键字,可以更轻松地实现ValidateUrlAsync方法,并完全支持异常处理(在上一示例中我没有做过,即如果发生故障,则不会实现)传达原因)。

public async Task<bool> ValidateUrlAsync(string url)
{
using(var response = (HttpWebResponse)await WebRequest.Create(url).GetResponseAsync())
return response.StatusCode == HttpStatusCode.Ok;
}

Even without the Async CTP, though, it's still possible to implement ValidateUrlAsync with this signature.

即使没有Async CTP,也仍然可以使用此签名来实现ValidateUrlAsync。

Notice the use of System.Threading.Tasks.TaskCompletionSource. From MSDN:

注意使用System.Threading.Tasks.TaskCompletionSource。 从MSDN:

In many scenarios, it is useful to enable a Task (Of(TResult)) to represent an external asynchronous operation. TaskCompletionSource (Of( TResult)) is provided for this purpose. It enables the creation of a task that can be handed out to consumers, and those consumers can use the members of the task as they would any other.

在许多情况下,启用任务(Of(TResult))表示外部异步操作很有用 为此目的提供了TaskCompletionSource(Of(TResult))。 它使创建可以分发给消费者的任务成为可能,并且这些消费者可以像使用其他任务一样使用任务的成员。

public Task<bool> ValidateUrlAsync(string url)
{
var tcs = new TaskCompletionSource<bool>();
var request = (HttpWebRequest)WebRequest.Create(url);
try
{
request.BeginGetResponse(iar =>
{
HttpWebResponse response = null;
try
{
response = (HttpWebResponse)request.EndGetResponse(iar);
tcs.SetResult(response.StatusCode == HttpStatusCode.OK);
}
catch(Exception exc) { tcs.SetException(exc); }
finally { if (response != null) response.Close(); }
}, null);
}
catch(Exception exc) { tcs.SetException(exc); }
return tsc.Task;

}

So, with this method, even without the Async CTP, we can use existing .NET 4 support to handle this relatively easily:

因此,使用这种方法,即使没有Async CTP,我们也可以使用现有的.NET 4支持相对轻松地处理此问题:

Task.Factory.ContinueWhenAll(
(from url in urls select ValidateUrlAsync(url)).ToArray(),
completedTasks => { /* do some end task */ });

Now, using just what comes with .NET 4 proper I get the best of all worlds.

现在,仅使用.NET 4附带的功能,我就能获得世界上最好的。

Big thanks to Stephen Toub. There's lots of new high- and low-level constructs for Task creation, Threading, and Parallelism in .NET 4. While the naive solution is often the right one, the components we have to work with in .NET 4 (and the even newer ones in the Visual Studio 2010 Async CTP adding the 'await' and 'async' keywords) will give you surprisingly fine-grained control over your multi-threaded parallel systems without a whole lot of code.

非常感谢Stephen Toub。 .NET 4中有许多用于任务创建,线程和并行性的新的高层和底层结构。尽管天真的解决方案通常是正确的解决方案,但我们必须在.NET 4中使用这些组件(甚至更新的)在Visual Studio 2010异步CTP中添加“ await”和“ async”关键字的代码)将使您出乎意料地对多线程并行系统进行细粒度控制,而无需编写大量代码。

Related Links:

相关链接:

翻译自: https://www.hanselman.com/blog/back-to-parallel-basics-dont-block-your-threads-make-async-io-work-for-you

并行 异步 多线程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值