使用HttpWebRequest.BeginGetResponse发送大量请求时部分未完成
问题排查与修复方案
嘿,我来帮你拆解下这个问题:你发起10000个异步HttpWebRequest,但最终完成计数总是少几十个,核心原因是线程安全问题,另外代码里还有个容易忽略的资源泄漏隐患,咱们一步步来解决。
为什么计数会“丢失”?
你定义的completed变量是在多个异步回调线程中被直接递增的(completed++),这个操作不是原子性的——当多个线程同时读取、修改、写回这个值时,会出现值覆盖的情况。比如线程A和线程B同时读到completed=500,各自加1后写回,结果最终completed变成501而不是502,这就直接导致了计数丢失。
另外,你的代码里用callback.BeginInvoke(...)但从未调用EndInvoke,这会导致异步委托的资源无法被正确释放,长期运行会造成内存泄漏,不过这不是计数缺失的直接原因,但必须修复。
修复步骤
1. 用原子操作保证计数器线程安全
把直接的completed++换成Interlocked.Increment,它能保证递增操作是原子性的,不会被其他线程打断:
var callback = new Action<String, Exception>((r, ex) => { // 用Interlocked.Increment原子递增,返回递增后的当前值 int currentCount = Interlocked.Increment(ref completed); Console.WriteLine(currentCount); });
2. 重构WebHelper,避免Begin/End模式的资源泄漏
原来的WebHelper用了嵌套的BeginInvoke,还没处理EndInvoke,这是个隐患。更推荐用现代的async/await来重构,代码更简洁且自动处理资源:
public static class WebHelper { // 异步版本的方法,用async/await替代Begin/End private static async Task<string> GetResponseStringAsync(WebRequest request) { using (var resp = await request.GetResponseAsync()) using (var sr = new StreamReader(resp.GetResponseStream())) { return await sr.ReadToEndAsync(); } } public static async Task ProcessRequestAsync(WebRequest request, Action<string, Exception> callback) { try { var response = await GetResponseStringAsync(request); callback(response, null); } catch (Exception ex) { callback(null, ex); } } // 保留原来的BeginGetResponse方法,包装异步逻辑 public static void BeginGetResponse(WebRequest request, Action<string, Exception> callback) { // 用_忽略返回的Task,避免编译器警告 _ = ProcessRequestAsync(request, callback); } }
3. 完整的TestingForm修改后代码
public partial class TestingForm : Form { public TestingForm() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { int completed = 0; var callback = new Action<String, Exception>((r, ex) => { int currentCount = Interlocked.Increment(ref completed); Console.WriteLine(currentCount); }); for (int i = 0; i < 10000; i++) { var req = HttpWebRequest.Create("http://example.com"); req.Proxy = new WebProxy("127.0.0.2:8888"); WebHelper.BeginGetResponse(req, callback); } } }
额外注意点
如果你的代理127.0.0.2:8888偶尔不可用或者请求超时,会不会影响计数?其实不会——因为你的代码里已经捕获了所有异常并调用了callback,所以哪怕请求失败,计数器还是会递增。所以修复线程安全问题后,最终计数应该能准确达到10000。
内容的提问来源于stack exchange,提问作者JohnWick
相关产品推荐
相关产品推荐

