它是安全调用的BeginXXX无需调用EndXXX如果实例已处置 [英] Is it safe to call BeginXXX without calling EndXXX if the instance is already disposed
问题描述
在使用异步编程模型一>,通常建议以匹配每个的BeginXXX
与 EndXXX
,否则你可能泄漏资源,直到异步操作完成
现在仍然是情况下,如果类实现的IDisposable
和实例的处置通过调用的Dispose
?
例如,如果我使用 UdpClient.BeginReceive
在 UdpListener
:
类UdpListener:IDisposable接口
{
私人布尔_isDisposed;
私人只读ip地址_hostIpAddress;
私人只读INT _port;
私人UdpClient _udpClient;
公共UdpListener(ip地址hostIpAddress,诠释端口)
{
_hostIpAddress = hostIpAddress;
_port =口;
}
公共无效启动()
{
_udpClient.Connect(_hostIpAddress,_port);
_udpClient.BeginReceive(的handleMessage,NULL);
}
公共无效的Dispose()
{
如果(_isDisposed)
{
抛出新的ObjectDisposedException(UdpListener);
}
((IDisposable接口)_udpClient).Dispose();
_isDisposed = TRUE;
}
私有静态无效的handleMessage(IAsyncResult的asyncResult)
{
// ...手柄
}
}
我还需要确保 UdpClient.EndReceive
被称为在处置 _udpClient
(这将导致刚在的ObjectDisposedException
)?
编辑:
这并非罕见前完成所有异步操作处置废弃 UdpClient
(和其他的IDisposable
S)一种方法来取消或执行超时,特别是在操作永远不会完成。这也是什么建议整个 这 网站。
在使用异步编程模型通常建议以匹配每个
的BeginXXX
与EndXXX
,否则,你的风险泄漏资源保存,而异步操作仍然是正在运行。
现在仍然是情况下,如果类实现
的IDisposable
和的Dispose
被称为上的实例?
块引用>这无关带班实施
的IDisposable
或没有。除非你能肯定的是,异步建成后,将腾出绑起来通过
的BeginXXX
启动异步操作的任何资源,并且不执行清理中,或作为在EndXXX
调用的结果,你需要确保你匹配您的来电。只有这样,才能为某些此,就是检查执行特定异步操作的。有关在 <一个href=\"https://msdn.microsoft.com/en-us/library/system.net.sockets.udpclient.beginreceive(v=vs.110).aspx\"相对=nofollow>
UdpClient
例如您选择,它正好的情况是:
- 电话
EndXXX
在处置的UDPClient
实例会导致它直接抛出的ObjectDisposedException
。- 无资源配置中或作为
EndXXX
调用的结果。- 的资源捆绑与此操作(原生重叠和牵制非托管缓存),将在异步操作完成回调被回收。
因此,在这种情况下,它是非常安全的,无泄漏
作为一个一般的方法
我不相信这种方式是作为一般的做法正确,因为:
- 的实施可能会改变未来,打破你的假设。
- 有更好的方法来做到这一点,使用注销和超时你的异步(I / O)操作(例如,通过调用
关闭
在_udpClient
实例强制I / O故障)。另外,我不希望靠我检查了整个调用堆栈(并且这样做不是犯了一个错误),以确保没有资源将被泄露。
推荐和记录方法
请注意,从的
UdpClient.BeginReceive
方法的文档如下:
异步
BeginReceive
操作的必须被调用EndReceive
方法完成。典型地,该方法是由requestCallback代表调用
块引用>和对底层 <$ C以下$ C> Socket.BeginReceive 方式:
异步
BeginReceive
操作的必须被调用EndReceive
方法完成。典型地,该方法是由回调委托调用
要取消挂起的
BeginReceive
,叫关闭
方法。
块引用>即。这就是设计记录的行为。你可以说设计是非常好的,但它是清楚的预期的方法来消除是,那你可以期望,因为这样做的结果的行为。
有关您的具体的例子(更新做一些与异步结果很有用)相似而已,和其它一些情况,下面就跟随推荐的方法实现:
公共类UdpListener:IDisposable接口
{
私人只读ip地址_hostIpAddress;
私人只读INT _port;
私人只读动作&LT;的Ud preceiveResult&GT; _处理器;
私人TaskCompletionSource&LT;布尔&GT; _tcs =新TaskCompletionSource&LT;布尔&GT;();
私人CancellationTokenSource _tokenSource =新CancellationTokenSource();
私人CancellationTokenRegistration _tokenReg;
私人UdpClient _udpClient; 公共UdpListener(ip地址hostIpAddress,INT端口,动作&LT;的Ud preceiveResult&GT;处理器)
{
_hostIpAddress = hostIpAddress;
_port =口;
_processor =处理器;
} 公共任务ReceiveAsync()
{
//注意:这里有一个竞争条件在并发呼叫的情况下,
如果(_tokenSource = NULL&放大器;!&安培; _udpClient == NULL)
{
尝试
{
_udpClient =新UdpClient();
_udpClient.Connect(_hostIpAddress,_port);
_tokenReg = _tokenSource.Token.Register(()=&GT; _udpClient.Close());
BeginReceive();
}
赶上(异常前)
{
_tcs.SetException(除息);
扔;
}
}
返回_tcs.Task;
} 公共无效停止()
{
VAR CTS = Interlocked.Exchange(REF _tokenSource,NULL);
如果(CTS!= NULL)
{
cts.Cancel();
如果(_tcs = NULL&放大器;!&安培;!_udpClient = NULL)
_tcs.Task.Wait();
_tokenReg.Dispose();
cts.Dispose();
}
} 公共无效的Dispose()
{
停止();
如果(_udpClient!= NULL)
{
((IDisposable接口)_udpClient).Dispose();
_udpClient = NULL;
}
GC.Sup pressFinalize(本);
} 私人无效BeginReceive()
{
VAR IAR = _udpClient.BeginReceive(的handleMessage,NULL);
如果(iar.CompletedSynchronously)
的handleMessage(IAR); //如果总是同步完成= GT;堆栈溢出
} 私人无效的handleMessage(IAsyncResult的IAR)
{
尝试
{
IPEndPoint remoteEP = NULL;
字节[]缓冲= _udpClient.EndReceive(IAR,楼盘remoteEP);
_processor(新的Ud preceiveResult(缓冲,remoteEP));
BeginReceive(); //做下一个
}
赶上(的ObjectDisposedException)
{
//我们被取消,即正常完成
_tcs.SetResult(真);
}
赶上(异常前)
{
//我们失败了。
_tcs.TrySetException(除息);
}
}
}When using the Asynchronous Programming Model it is usually recommended to match every
BeginXXX
with anEndXXX
, otherwise you risk leaking resources until the asynchronous operation completes.Is that still the case if the class implements
IDisposable
and the instance was disposed by callingDispose
?If for example I use
UdpClient.BeginReceive
in aUdpListener
:class UdpListener : IDisposable { private bool _isDisposed; private readonly IPAddress _hostIpAddress; private readonly int _port; private UdpClient _udpClient; public UdpListener(IPAddress hostIpAddress, int port) { _hostIpAddress = hostIpAddress; _port = port; } public void Start() { _udpClient.Connect(_hostIpAddress, _port); _udpClient.BeginReceive(HandleMessage, null); } public void Dispose() { if (_isDisposed) { throw new ObjectDisposedException("UdpListener"); } ((IDisposable) _udpClient).Dispose(); _isDisposed = true; } private static void HandleMessage(IAsyncResult asyncResult) { // handle... } }
Do I still need to make sure
UdpClient.EndReceive
is called on the disposed_udpClient
(which will just result in anObjectDisposedException
)?
Edit:
It isn't uncommon to dispose a
UdpClient
(and otherIDisposable
s) before all asynchronous operations completed as a way to cancel or implement a timeout, especially over operations that will never complete. This is also what's recommended throughout this site.解决方案When using the Asynchronous Programming Model it is usually recommended to match every
BeginXXX
with anEndXXX
, otherwise you risk leaking resources kept while the asynchronous operation is still "running".Is that still the case if the class implements
IDisposable
andDispose
was called on the instance?This has nothing to do with a class implementing
IDisposable
or not.Unless you can be sure that the async completion will free up any resources tied up with the async operation initiated through
BeginXXX
, and no cleanup is performed in, or as a result of theEndXXX
call, you need to ensure that you match your calls. The only way to be certain of this, is to inspect the implementation of a specific async operation.For the
UdpClient
example you chose, it happens to be the case that:
- Calling
EndXXX
after disposing theUDPClient
instance will result in it directly throwing anObjectDisposedException
.- No resources are disposed in or as a result of the
EndXXX
call.- The resources tied up with this operation (native overlapped and pinned unmanaged buffers), will be recycled on the async operation completion callback.
So in this case it is perfectly safe, without leakage.
As a general approach
I don't believe this approach is correct as a general approach, because:
- The implementation could change in the future, breaking your assumptions.
- There are better ways to do this, using cancellation and time-outs for your async (I/O) operations (e.g. by calling
Close
on the_udpClient
instance to force an I/O failure).Also, I would not want to rely on me inspecting the entire call stack (and not making a mistake in doing so) to ensure that no resources will be leaked.
Recommended and documented approach
Please note the following from the documentation for the
UdpClient.BeginReceive
method:The asynchronous
BeginReceive
operation must be completed by calling theEndReceive
method. Typically, the method is invoked by the requestCallback delegate.And the following for the underlying
Socket.BeginReceive
method:The asynchronous
BeginReceive
operation must be completed by calling theEndReceive
method. Typically, the method is invoked by the callback delegate.To cancel a pending
BeginReceive
, call theClose
method.I.e. this is the "by design" documented behavior. You can argue whether the design is very good, but it is clear in what the expected approach to cancellation is, and the behavior that you can expect as the result of doing so.
For your specific example (updated to do something useful with the async result), and other situations similar to it, the following would be an implementation that follows the recommended approach:
public class UdpListener : IDisposable { private readonly IPAddress _hostIpAddress; private readonly int _port; private readonly Action<UdpReceiveResult> _processor; private TaskCompletionSource<bool> _tcs = new TaskCompletionSource<bool>(); private CancellationTokenSource _tokenSource = new CancellationTokenSource(); private CancellationTokenRegistration _tokenReg; private UdpClient _udpClient; public UdpListener(IPAddress hostIpAddress, int port, Action<UdpReceiveResult> processor) { _hostIpAddress = hostIpAddress; _port = port; _processor = processor; } public Task ReceiveAsync() { // note: there is a race condition here in case of concurrent calls if (_tokenSource != null && _udpClient == null) { try { _udpClient = new UdpClient(); _udpClient.Connect(_hostIpAddress, _port); _tokenReg = _tokenSource.Token.Register(() => _udpClient.Close()); BeginReceive(); } catch (Exception ex) { _tcs.SetException(ex); throw; } } return _tcs.Task; } public void Stop() { var cts = Interlocked.Exchange(ref _tokenSource, null); if (cts != null) { cts.Cancel(); if (_tcs != null && _udpClient != null) _tcs.Task.Wait(); _tokenReg.Dispose(); cts.Dispose(); } } public void Dispose() { Stop(); if (_udpClient != null) { ((IDisposable)_udpClient).Dispose(); _udpClient = null; } GC.SuppressFinalize(this); } private void BeginReceive() { var iar = _udpClient.BeginReceive(HandleMessage, null); if (iar.CompletedSynchronously) HandleMessage(iar); // if "always" completed sync => stack overflow } private void HandleMessage(IAsyncResult iar) { try { IPEndPoint remoteEP = null; Byte[] buffer = _udpClient.EndReceive(iar, ref remoteEP); _processor(new UdpReceiveResult(buffer, remoteEP)); BeginReceive(); // do the next one } catch (ObjectDisposedException) { // we were canceled, i.e. completed normally _tcs.SetResult(true); } catch (Exception ex) { // we failed. _tcs.TrySetException(ex); } } }
这篇关于它是安全调用的BeginXXX无需调用EndXXX如果实例已处置的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!