C# HttpClient 和 HttpClientHandler 是否必须在请求之间进行处理?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15705092/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Do HttpClient and HttpClientHandler have to be disposed between requests?
提问by Fernando Correia
System.Net.Http.HttpClientand System.Net.Http.HttpClientHandlerin .NET Framework 4.5 implement IDisposable (via System.Net.Http.HttpMessageInvoker).
.NET Framework 4.5 中的System.Net.Http.HttpClient和System.Net.Http.HttpClientHandler实现 IDisposable(通过System.Net.Http.HttpMessageInvoker)。
The using
statement documentation says:
该using
声明文件说:
As a rule, when you use an IDisposable object, you should declare and instantiate it in a using statement.
通常,当您使用 IDisposable 对象时,您应该在 using 语句中声明并实例化它。
This answeruses this pattern:
这个答案使用这个模式:
var baseAddress = new Uri("http://example.com");
var cookieContainer = new CookieContainer();
using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
{
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("foo", "bar"),
new KeyValuePair<string, string>("baz", "bazinga"),
});
cookieContainer.Add(baseAddress, new Cookie("CookieName", "cookie_value"));
var result = client.PostAsync("/test", content).Result;
result.EnsureSuccessStatusCode();
}
But the most visible examples from Microsoft don't call Dispose()
either explicitly or implicitly. For instance:
但是微软最明显的例子没有Dispose()
显式或隐式调用。例如:
- The original blog articleannouncing the relase of HttpClient.
- The actual MSDN documentationfor HttpClient.
- BingTranslateSample
- GoogleMapsSample
- WorldBankSample
In the announcement's comments, someone asked the Microsoft employee:
在公告的评论中,有人问微软员工:
After checking your samples, I saw that you didn't perform the dispose action on HttpClient instance. I have used all instances of HttpClient with using statement on my app and I thought that it is the right way since HttpClient implements the IDisposable interface. Am I on the right path?
检查您的示例后,我发现您没有对 HttpClient 实例执行处置操作。我在我的应用程序上使用了带有 using 语句的 HttpClient 的所有实例,我认为这是正确的方法,因为 HttpClient 实现了 IDisposable 接口。我在正确的道路上吗?
His answer was:
他的回答是:
In general that is correct although you have to be careful with "using" and async as they dont' really mix in .Net 4, In .Net 4.5 you can use "await" inside a "using" statement.
Btw, you can reuse the same HttpClient as many times are [as] you like so typically you won't create/dispose them all the time.
一般来说,这是正确的,尽管您必须小心“使用”和异步,因为它们并没有真正混入 .Net 4,在 .Net 4.5 中,您可以在“使用”语句中使用“等待”。
顺便说一句,您可以根据自己的喜好重复使用相同的 HttpClient,因此通常您不会一直创建/处置它们。
The second paragraph is superfluous to this question, which is not concerned about how many times you can use an HttpClient instance, but about if it is necessary to dispose it after you no longer need it.
第二段对这个问题来说是多余的,它不是关心你可以使用多少次 HttpClient 实例,而是在你不再需要它之后是否需要处置它。
(Update: in fact that second paragraph is the key to the answer, as provided below by @DPeden.)
(更新:事实上,第二段是答案的关键,如下面由@DPeden 提供的。)
So my questions are:
所以我的问题是:
Is it necessary, given the current implementation (.NET Framework 4.5), to call Dispose() on HttpClient and HttpClientHandler instances? Clarification: by "necessary" I mean if there are any negative consequences for not disposing, such as resource leakage or data corruption risks.
If it's not necessary, would it be a "good practice" anyway, since they implement IDisposable?
If it's necessary (or recommended), is this codementioned above implementing it safely (for .NET Framework 4.5)?
If these classes don't require calling Dispose(), why were they implemented as IDisposable?
If they require, or if it's a recommended practice, are the Microsoft examples misleading or unsafe?
鉴于当前的实现(.NET Framework 4.5),是否有必要在 HttpClient 和 HttpClientHandler 实例上调用 Dispose()?澄清:“必要”是指如果不处置会产生任何负面后果,例如资源泄漏或数据损坏风险。
如果没有必要,既然它们实现了 IDisposable,那么无论如何这都是一个“好习惯”吗?
如果有必要(或推荐),上面提到的这段代码是否安全地实现了它(对于 .NET Framework 4.5)?
如果这些类不需要调用 Dispose(),为什么它们被实现为 IDisposable?
如果他们需要,或者如果这是推荐的做法,Microsoft 示例是否具有误导性或不安全性?
采纳答案by David Peden
The general consensus is that you do not (should not) need to dispose of HttpClient.
普遍的共识是您不需要(不应该)处理 HttpClient。
Many people who are intimately involved in the way it works have stated this.
许多密切参与其运作方式的人都说过这一点。
See Darrel Miller's blog postand a related SO post: HttpClient crawling results in memory leakfor reference.
请参阅Darrel Miller 的博客文章和相关的 SO 文章:HttpClient crawling results in memory leak,以供参考。
I'd also strongly suggest that you read the HttpClient chapter from Designing Evolvable Web APIs with ASP.NETfor context on what is going on under the hood, particularly the "Lifecycle" section quoted here:
我还强烈建议您阅读Designing Evolvable Web APIs with ASP.NET 中的 HttpClient 章节,了解幕后发生的事情,特别是此处引用的“生命周期”部分:
Although HttpClient does indirectly implement the IDisposable interface, the standard usage of HttpClient is not to dispose of it after every request. The HttpClient object is intended to live for as long as your application needs to make HTTP requests. Having an object exist across multiple requests enables a place for setting DefaultRequestHeaders and prevents you from having to re-specify things like CredentialCache and CookieContainer on every request as was necessary with HttpWebRequest.
虽然 HttpClient 确实间接实现了 IDisposable 接口,但 HttpClient 的标准用法是不要在每次请求后处理它。只要您的应用程序需要发出 HTTP 请求,HttpClient 对象就会一直存在。跨多个请求存在一个对象可以为设置 DefaultRequestHeaders 提供一个位置,并防止您必须像 HttpWebRequest 那样在每个请求上重新指定 CredentialCache 和 CookieContainer 之类的东西。
Or even open up DotPeek.
或者甚至打开 DotPeek。
回答by svidgen
In my understanding, calling Dispose()
is necessary only when it's locking resources you need later (like a particular connection). It's always recommendedto free resources you're no longer using, even if you don't need them again, simply because you shouldn't generallybe holding onto resources you're not using (pun intended).
根据我的理解,Dispose()
只有在锁定您以后需要的资源(如特定连接)时才需要调用。始终建议释放您不再使用的资源,即使您不再需要它们,只是因为您通常不应该保留您不使用的资源(双关语)。
The Microsoft example is not incorrect, necessarily. All resources used will be released when the application exits. And in the case of that example, that happens almost immediately after the HttpClient
is done being used. In like cases, explicitly calling Dispose()
is somewhat superfluous.
微软的例子不一定不正确。当应用程序退出时,所有使用的资源将被释放。在那个例子的情况下,这几乎在使用HttpClient
完成后立即发生。在类似的情况下,显式调用Dispose()
有点多余。
But, in general, when a class implements IDisposable
, the understanding is that you should Dispose()
of its instances as soon as you're fully ready and able. I'd posit this is particularly true in cases like HttpClient
wherein it's not explicitly documented as to whether resources or connections are being held onto/open. In the case wherein the connection will be reused again [soon], you'll want to forgo Dipose()
ing of it -- you're not "fully ready" in that case.
但是,一般来说,当一个类实现 时IDisposable
,理解是Dispose()
一旦您完全准备好并且有能力,您就应该对其实例进行处理。我认为在HttpClient
没有明确记录资源或连接是否被保留/打开的情况下尤其如此。如果连接将 [很快] 再次被重用,你会想要放弃Dipose()
它——在这种情况下你还没有“完全准备好”。
See also: IDisposable.Dispose Methodand When to call Dispose
回答by David Faivre
In my case, I was creating an HttpClient inside a method that actually did the service call. Something like:
就我而言,我在实际执行服务调用的方法中创建了一个 HttpClient。就像是:
public void DoServiceCall() {
var client = new HttpClient();
await client.PostAsync();
}
In an Azure worker role, after repeatedly calling this method (without disposing the HttpClient), it would eventually fail with SocketException
(connection attempt failed).
在 Azure 辅助角色中,在重复调用此方法后(没有处理 HttpClient),它最终会失败SocketException
(连接尝试失败)。
I made the HttpClient an instance variable (disposing it at the class level) and the issue went away. So I would say, yes, dispose the HttpClient, assuming its safe (you don't have outstanding async calls) to do so.
我使 HttpClient 成为一个实例变量(在类级别处理它),问题就消失了。所以我会说,是的,处理 HttpClient,假设这样做是安全的(您没有未完成的异步调用)。
回答by yayadavid
I think one should use singleton pattern to avoid having to create instances of the HttpClient and closing it all the time. If you are using .Net 4.0 you could use a sample code as below. for more information on singleton pattern check here.
我认为应该使用单例模式来避免创建 HttpClient 的实例并一直关闭它。如果您使用的是 .Net 4.0,您可以使用如下示例代码。有关单例模式检查的更多信息,请点击此处。
class HttpClientSingletonWrapper : HttpClient
{
private static readonly Lazy<HttpClientSingletonWrapper> Lazy= new Lazy<HttpClientSingletonWrapper>(()=>new HttpClientSingletonWrapper());
public static HttpClientSingletonWrapper Instance {get { return Lazy.Value; }}
private HttpClientSingletonWrapper()
{
}
}
Use the code as below.
使用代码如下。
var client = HttpClientSingletonWrapper.Instance;
回答by Tom Deseyn
In typical usage (responses<2GB) it is not necessary to Dispose the HttpResponseMessages.
在典型用法(响应<2GB)中,不需要处理 HttpResponseMessages。
The return types of the HttpClient methods should be Disposed if their Stream Content is not fully Read. Otherwise there is no way for the CLR to know those Streams can be closed until they are garbage collected.
如果 HttpClient 方法的流内容未完全读取,则其返回类型应为 Disposed。否则 CLR 无法知道这些 Streams 可以关闭,直到它们被垃圾收集。
- If you are reading the data into a byte[] (e.g. GetByteArrayAsync) or string, all data is read, so there is no need to dispose.
- The other overloads will default to reading the Stream up to 2GB (HttpCompletionOption is ResponseContentRead, HttpClient.MaxResponseContentBufferSize default is 2GB)
- 如果将数据读入 byte[](例如 GetByteArrayAsync)或字符串,则读取所有数据,因此无需处置。
- 其他重载将默认读取高达 2GB 的 Stream(HttpCompletionOption 为 ResponseContentRead,HttpClient.MaxResponseContentBufferSize 默认为 2GB)
If you set the HttpCompletionOption to ResponseHeadersRead or the response is larger than 2GB, you should clean up. This can be done by calling Dispose on the HttpResponseMessage or by calling Dispose/Close on the Stream obtained from the HttpResonseMessage Content or by reading the content completely.
如果将 HttpCompletionOption 设置为 ResponseHeadersRead 或响应大于 2GB,则应进行清理。这可以通过在 HttpResponseMessage 上调用 Dispose 或通过在从 HttpResonseMessage 内容获得的 Stream 上调用 Dispose/Close 或通过完全读取内容来完成。
Whether you call Dispose on the HttpClient depends on whether you want to cancel pending requests or not.
是否在 HttpClient 上调用 Dispose 取决于您是否要取消挂起的请求。
回答by Ohad Schneider
The current answers are a bit confusing and misleading, and they are missing some important DNS implications. I'll try to summarize where things stand clearly.
当前的答案有点令人困惑和误导,并且它们遗漏了一些重要的 DNS 含义。我将尝试总结清楚的地方。
- Generally speaking most
IDisposable
objects should ideally be disposed when you are done with them, especially those that own Named/shared OS resources.HttpClient
is no exception, since as Darrel Millerpoints out it allocates cancellation tokens, and request/response bodies can be unmanaged streams. - However, the best practice for HttpClientsays you should create one instance and reuse it as much as possible (using its thread-safe membersin multi-threaded scenarios). Therefore, in most scenarios you'll never dispose of it simply because you will be needing it all the time.
- The problem with re-using the same HttpClient "forever" is that the underlying HTTP connection might remain open against the originally DNS-resolved IP, regardless of DNS changes. This can be an issue in scenarios like blue/green deployment and DNS-based failover. There are various approaches for dealing with this issue, the most reliable one involving the server sending out a
Connection:close
header after DNS changes take place. Another possibility involves recycling theHttpClient
on the client side, either periodically or via some mechanism that learns about the DNS change. See https://github.com/dotnet/corefx/issues/11224for more information (I suggest reading it carefully before blindly using the code suggested in the linked blog post).
- 一般来说,大多数
IDisposable
对象最好在您处理完后就被处理掉,尤其是那些拥有命名/共享操作系统资源的对象。HttpClient
也不例外,因为Darrel Miller指出它分配取消令牌,并且请求/响应主体可以是非托管流。 - 但是,HttpClient的最佳实践是创建一个实例并尽可能多地重用它(在多线程场景中使用其线程安全成员)。因此,在大多数情况下,您永远不会仅仅因为您一直需要它而将其丢弃。
- “永远”重复使用相同的 HttpClient 的问题在于,无论 DNS 发生什么变化,底层 HTTP 连接都可能对最初由 DNS 解析的 IP 保持打开状态。在蓝/绿部署和基于 DNS 的故障转移等场景中,这可能是一个问题。有多种方法可以解决此问题,最可靠的方法是
Connection:close
在 DNS 更改发生后服务器发送标头。另一种可能性涉及HttpClient
在客户端回收,定期或通过某种了解 DNS 更改的机制。有关更多信息,请参阅https://github.com/dotnet/corefx/issues/11224(我建议在盲目使用链接博客文章中建议的代码之前仔细阅读)。
回答by Timothy Gonzalez
Dispose() calls the code below, which closes the connections opened by the HttpClient instance. The code was created by decompiling with dotPeek.
Dispose() 调用下面的代码,关闭由 HttpClient 实例打开的连接。该代码是通过使用 dotPeek 反编译创建的。
HttpClientHandler.cs - Dispose
HttpClientHandler.cs - 处置
ServicePointManager.CloseConnectionGroups(this.connectionGroupName);
If you don't call dispose then ServicePointManager.MaxServicePointIdleTime, which runs by a timer, will close the http connections. The default is 100 seconds.
如果您不调用 dispose,则由计时器运行的 ServicePointManager.MaxServicePointIdleTime 将关闭 http 连接。默认值为 100 秒。
ServicePointManager.cs
服务点管理器.cs
internal static readonly TimerThread.Callback s_IdleServicePointTimeoutDelegate = new TimerThread.Callback(ServicePointManager.IdleServicePointTimeoutCallback);
private static volatile TimerThread.Queue s_ServicePointIdlingQueue = TimerThread.GetOrCreateQueue(100000);
private static void IdleServicePointTimeoutCallback(TimerThread.Timer timer, int timeNoticed, object context)
{
ServicePoint servicePoint = (ServicePoint) context;
if (Logging.On)
Logging.PrintInfo(Logging.Web, SR.GetString("net_log_closed_idle", (object) "ServicePoint", (object) servicePoint.GetHashCode()));
lock (ServicePointManager.s_ServicePointTable)
ServicePointManager.s_ServicePointTable.Remove((object) servicePoint.LookupString);
servicePoint.ReleaseAllConnectionGroups();
}
If you haven't set the idle time to infinite then it appears safe not to call dispose and let the idle connection timer kick-in and close the connections for you, although it would be better for you to call dispose in a using statement if you know you are done with an HttpClient instance and free up the resources faster.
如果您尚未将空闲时间设置为无限,那么不调用 dispose 并让空闲连接计时器启动并为您关闭连接似乎是安全的,尽管如果您在 using 语句中调用 dispose 会更好您知道您已完成 HttpClient 实例并更快地释放资源。
回答by TamusJRoyce
If you want to dispose of HttpClient, you can if you set it up as a resource pool. And at the end of your application, you dispose your resource pool.
如果你想处理HttpClient,你可以把它设置为资源池。在您的应用程序结束时,您将处置您的资源池。
Code:
代码:
// Notice that IDisposable is not implemented here!
public interface HttpClientHandle
{
HttpRequestHeaders DefaultRequestHeaders { get; }
Uri BaseAddress { get; set; }
// ...
// All the other methods from peeking at HttpClient
}
public class HttpClientHander : HttpClient, HttpClientHandle, IDisposable
{
public static ConditionalWeakTable<Uri, HttpClientHander> _httpClientsPool;
public static HashSet<Uri> _uris;
static HttpClientHander()
{
_httpClientsPool = new ConditionalWeakTable<Uri, HttpClientHander>();
_uris = new HashSet<Uri>();
SetupGlobalPoolFinalizer();
}
private DateTime _delayFinalization = DateTime.MinValue;
private bool _isDisposed = false;
public static HttpClientHandle GetHttpClientHandle(Uri baseUrl)
{
HttpClientHander httpClient = _httpClientsPool.GetOrCreateValue(baseUrl);
_uris.Add(baseUrl);
httpClient._delayFinalization = DateTime.MinValue;
httpClient.BaseAddress = baseUrl;
return httpClient;
}
void IDisposable.Dispose()
{
_isDisposed = true;
GC.SuppressFinalize(this);
base.Dispose();
}
~HttpClientHander()
{
if (_delayFinalization == DateTime.MinValue)
_delayFinalization = DateTime.UtcNow;
if (DateTime.UtcNow.Subtract(_delayFinalization) < base.Timeout)
GC.ReRegisterForFinalize(this);
}
private static void SetupGlobalPoolFinalizer()
{
AppDomain.CurrentDomain.ProcessExit +=
(sender, eventArgs) => { FinalizeGlobalPool(); };
}
private static void FinalizeGlobalPool()
{
foreach (var key in _uris)
{
HttpClientHander value = null;
if (_httpClientsPool.TryGetValue(key, out value))
try { value.Dispose(); } catch { }
}
_uris.Clear();
_httpClientsPool = null;
}
}
var handler = HttpClientHander.GetHttpClientHandle(new Uri("base url")).
var handler = HttpClientHander.GetHttpClientHandle(new Uri("base url"))。
- HttpClient, as an interface, can't call Dispose().
- Dispose() will be called in a delayed fashion by the Garbage Collector. Or when the program cleans up the object through its destructor.
- Uses Weak References + delayed cleanup logic so it remains in use so long as it is being reused frequently.
- It only allocates a new HttpClient for each base URL passed to it. Reasons explained by Ohad Schneider answer below. Bad behavior when changing base url.
- HttpClientHandle allows for Mocking in tests
- HttpClient 作为接口,不能调用 Dispose()。
- Dispose() 将被垃圾收集器以延迟方式调用。或者当程序通过其析构函数清理对象时。
- 使用弱引用 + 延迟清理逻辑,因此只要经常重用它,它就会一直使用。
- 它只为传递给它的每个基本 URL 分配一个新的 HttpClient。Ohad Schneider 解释的原因如下。更改基本 url 时的不良行为。
- HttpClientHandle 允许在测试中进行 Mocking
回答by alastairtree
Using dependency injection in your constructor makes managing the lifetime of your HttpClient
easier - taking the lifetime managemant outside of the code that needs it and making it easily changable at a later date.
在构造函数中使用依赖注入可以HttpClient
更轻松地管理生命周期- 将生命周期管理器置于需要它的代码之外,并使其在以后轻松更改。
My current preference is to create a seperate http client class that inherits from HttpClient
once per target endpoint domainand then make it a singleton using dependency injection. public class ExampleHttpClient : HttpClient { ... }
我目前的偏好是创建一个单独的 http 客户端类,该类从HttpClient
每个目标端点域继承一次,然后使用依赖注入使其成为单例。public class ExampleHttpClient : HttpClient { ... }
Then I take a constructor dependency on the custom http client in the service classes where I need access to that API. This solves the lifetime problem and has advantages when it comes to connection pooling.
然后,我在需要访问该 API 的服务类中使用对自定义 http 客户端的构造函数依赖。这解决了生命周期问题,并且在连接池方面具有优势。
You can see a worked example in related answer at https://stackoverflow.com/a/50238944/3140853
您可以在https://stackoverflow.com/a/50238944/3140853 的相关答案中看到一个工作示例
回答by RayLuo
Short answer: No, the statement in the currently accepted answer is NOT accurate: "The general consensus is that you do not (should not) need to dispose of HttpClient".
简短回答:不,当前接受的答案中的陈述不准确:“普遍的共识是您不需要(不应该)处理 HttpClient”。
Long answer: BOTH of the following statements are true and achieveable at the same time:
长答案:以下两个陈述都是真实的并且可以同时实现:
- "HttpClient is intended to be instantiated once and re-used throughout the life of an application", quoted from official documentation.
- An
IDisposable
object is supposed/recommended to be disposed.
- “HttpClient 旨在实例化一次并在应用程序的整个生命周期中重复使用”,引自官方文档。
IDisposable
假设/建议处置一个对象。
And they DO NOT NECESSARILY CONFLICT with each other. It is just a matter of how you organize your code to reuse an HttpClient
AND still dispose it properly.
而且它们之间没有必然的冲突。这只是您如何组织代码以重用并且HttpClient
仍然正确处置它的问题。
An even longer answerquoted from my another answer:
甚至更长的答案从我引述另一种答案:
It is not a coincidence to see people
in some blog postsblaming how HttpClient
's IDisposable
interface
makes them tend to use the using (var client = new HttpClient()) {...}
pattern
and then lead to exhausted socket handler problem.
在一些博客文章中看到人们指责HttpClient
的IDisposable
接口如何使他们倾向于使用该using (var client = new HttpClient()) {...}
模式,然后导致耗尽套接字处理程序问题,这并非巧合。
I believe that comes down to an unspoken (mis?)conception: "an IDisposable object is expected to be short-lived".
我相信这归结为一个不言而喻的(错误?)概念: “一个 IDisposable 对象预计是短暂的”。
HOWEVER, while it certainly looks like a short-lived thing when we write code in this style:
然而,虽然当我们以这种风格编写代码时,它看起来确实是一件短暂的事情:
using (var foo = new SomeDisposableObject())
{
...
}
the official documentation on IDisposablenever mentions IDisposable
objects have to be short-lived.
By definition, IDisposable is merely a mechanism to allow you to release unmanaged resources.
Nothing more. In that sense, you are EXPECTED to eventually trigger the disposal,
but it does not require you to do so in a short-lived fashion.
IDisposable的官方文档从未提到IDisposable
对象必须是短暂的。根据定义,IDisposable 只是一种允许您释放非托管资源的机制。而已。从这个意义上说,您预计最终会触发处置,但这并不要求您以短暂的方式这样做。
It is therefore your job to properly choose when to trigger the disposal, base on your real object's life cycle requirement. There is nothing stopping you from using an IDisposable in a long-lived way:
因此,您的工作是根据真实对象的生命周期要求正确选择触发处置的时间。没有什么能阻止您长期使用 IDisposable:
using System;
namespace HelloWorld
{
class Hello
{
static void Main()
{
Console.WriteLine("Hello World!");
using (var client = new HttpClient())
{
for (...) { ... } // A really long loop
// Or you may even somehow start a daemon here
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
With this new understanding, now we revisit that blog post,
we can clearly notice that the "fix" initializes HttpClient
once but never dispose it,
that is why we can see from its netstat output that,
the connection remains at ESTABLISHED state which means it has NOT been properly closed.
If it were closed, its state would be in TIME_WAIT instead.
In practice, it is not a big deal to leak only one connection open after your entire program ends,
and the blog poster still see a performance gain after the fix;
but still, it is conceptually incorrect to blame IDisposable and choose to NOT dispose it.
有了这个新的理解,现在我们重温那篇博文,我们可以清楚地注意到“修复”初始化HttpClient
一次但从未处理过它,这就是为什么我们可以从它的 netstat 输出中看到,连接保持在 ESTABLISHED 状态,这意味着它已经未正确关闭。如果它被关闭,它的状态将改为 TIME_WAIT。在实践中,在你的整个程序结束后只泄漏一个打开的连接并不是什么大问题,并且博客发布者仍然看到修复后的性能提升;但是,归咎于 IDisposable 并选择不处理它在概念上是不正确的。