gpt4 book ai didi

c# - 如果实例已被处置,则在不调用 EndXXX 的情况下调用 BeginXXX 是否安全

转载 作者:太空狗 更新时间:2023-10-30 01:17:50 24 4
gpt4 key购买 nike

当使用 Asynchronous Programming Model 时,通常建议将每个 BeginXXXEndXXX 匹配,否则您可能会在异步操作完成之前泄漏资源。

如果类实现了 IDisposable 并且实例是通过调用 Dispose 处理的,情况仍然如此吗?

例如,如果我在 UdpListener 中使用 UdpClient.BeginReceive:

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...
}
}

我是否仍需要确保在已处置的 _udpClient 上调用了 UdpClient.EndReceive(这只会导致 ObjectDisposedException)?


编辑:

在所有异步操作完成之前释放 UdpClient(和其他 IDisposable)作为取消或实现超时的一种方式并不少见,尤其是超过 operations that will never complete .这也是推荐的 throughout this site

最佳答案

When using the Asynchronous Programming Model it is usually recommended to match every BeginXXX with an EndXXX, otherwise you risk leaking resources kept while the asynchronous operation is still "running".

Is that still the case if the class implements IDisposable and Dispose was called on the instance?

这与是否实现 IDisposable 的类无关。

除非您可以确定异步完成将释放与通过 BeginXXX 启动的异步操作相关的任何资源,并且没有执行清理,或者作为 EndXXX 调用的结果,您需要确保您匹配您的电话。要确定这一点的唯一方法是检查特定异步操作的实现

对于您选择的 UdpClient 示例,情况恰好是:

  1. 调用 EndXXX 处理 UDPClient 实例将导致它直接抛出 ObjectDisposedException
  2. EndXXX 调用中或因调用而未处置任何资源。
  3. 与此操作相关的资源( native 重叠和固定非托管缓冲区)将在异步操作完成回调中回收。

所以在这种情况下它是绝对安全的,没有泄漏。

作为一般方法

我不认为这种方法作为一般方法是正确的,因为:

  1. 实现方式将来可能会发生变化,从而打破您的假设。
  2. 有更好的方法可以做到这一点,对您的异步 (I/O) 操作使用取消和超时(例如,通过在 Close 实例上调用 _udpClient 来强制 I/O 失败)。<

此外,我不想依赖我检查整个调用堆栈(并且在这样做时没有犯错)来确保不会泄漏任何资源。

推荐和记录的方法

请注意 UdpClient.BeginReceive 方法文档中的以下内容:

The asynchronous BeginReceive operation must be completed by calling the EndReceive method. Typically, the method is invoked by the requestCallback delegate.

以及底层 Socket.BeginReceive 方法的以下内容:

The asynchronous BeginReceive operation must be completed by calling the EndReceive method. Typically, the method is invoked by the callback delegate.

To cancel a pending BeginReceive, call the Close method.

即这是“按设计”记录的行为。您可以争论设计是否非常好,但很清楚预期的取消方法是什么,以及这样做的结果是您可以预期的行为。

对于您的特定示例(已更新以对异步结果执行一些有用的操作)以及其他类似情况,以下是遵循推荐方法的实现:

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);
}
}
}

关于c# - 如果实例已被处置,则在不调用 EndXXX 的情况下调用 BeginXXX 是否安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30222269/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com