C# 如果实例已被释放,则调用BeginXXX而不调用EndXXX是否安全

C# 如果实例已被释放,则调用BeginXXX而不调用EndXXX是否安全,c#,.net,asynchronous,memory-leaks,idisposable,C#,.net,Asynchronous,Memory Leaks,Idisposable,使用时,通常建议将每个BeginXXX与EndXXX匹配,否则在异步操作完成之前,您可能会泄漏资源 如果类实现了IDisposable,并且实例是通过调用Dispose来处理的,那么情况仍然是这样吗 例如,如果我在UdpListener中使用UdpClient.BeginReceive: class UdpListener : IDisposable { private bool _isDisposed; private readonly IPAddress _hostIpAdd

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

如果类实现了
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...
    }
}
我是否仍需要确保对已处置的
\u UdpClient
调用
UdpClient.EndReceive
(这只会导致
ObjectDisposedException


编辑:

在所有异步操作完成之前处理
UdpClient
(以及其他
IDisposable
s)作为取消或实现超时的一种方式并不少见,尤其是在超时之后。这也是推荐的

在使用异步编程模型时,通常建议将每个
BeginXXX
EndXXX
匹配,否则在异步操作仍在“运行”时,可能会泄漏保留的资源

如果类实现了
IDisposable
,并且对实例调用了
Dispose
,那么情况仍然如此吗

这与是否实现
IDisposable
的类无关

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

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

  • 在处理
    UDPClient
    实例后调用
    EndXXX
    将导致它直接抛出
    ObjectDisposedException
  • EndXXX
    调用中或调用结果中不会释放任何资源
  • 与此操作相关的资源(本机重叠和固定的非托管缓冲区)将在异步操作完成回调上回收
  • 因此,在这种情况下,它是完全安全的,没有泄漏

    作为一般方法

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

  • 实施可能会在未来发生变化,打破您的假设
  • 有更好的方法可以做到这一点,对异步(I/O)操作使用取消和超时(例如,在
    \u udpClient
    实例上调用
    Close
    以强制I/O失败)
  • 此外,我不想依靠我检查整个调用堆栈(并且在这样做时没有犯错误)来确保没有资源泄漏

    推荐和记录的方法

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

    异步
    BeginReceive
    操作必须通过调用
    EndReceive
    方法来完成。通常,该方法由requestCallback委托调用

    对于基础方法,请执行以下操作:

    异步
    BeginReceive
    操作必须通过调用
    EndReceive
    方法来完成。通常,该方法由回调委托调用

    要取消挂起的
    BeginReceive
    ,请调用
    Close
    方法

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

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

    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); 
            }
        }
    }
    
    公共类UdpListener:IDisposable
    {
    专用只读IP地址\u主机IP地址;
    专用只读int_端口;
    专用只读操作处理器;
    私有TaskCompletionSource_tcs=新TaskCompletionSource();
    private CancellationTokenSource _tokenSource=新的CancellationTokenSource();
    私有取消令牌注册\u令牌注册;
    私有UDP客户_UDP客户;
    公共UdpListener(IPAddress主机IPAddress、int端口、操作处理器)
    {
    _hostIpAddress=hostIpAddress;
    _端口=端口;
    _处理器=处理器;
    }
    公共任务ReceiveAsync()
    {
    //注意:在并发调用的情况下,这里有一个竞争条件
    if(_tokenSource!=null&&u udpClient==null)
    {
    尝试
    {
    _udpClient=新的udpClient();
    _udpClient.Connect(_主机地址,_端口);
    _tokenReg=\u tokenSource.Token.Register(()=>\u udpClient.Close());
    BeginReceive();
    }
    捕获(例外情况除外)
    {
    _tcs.SetException(ex);
    投掷;
    }
    }
    返回_tcs.Task;
    }
    公共停车场()
    {
    var cts=Interlocked.Exchange(ref\u tokenSource,null);
    如果(cts!=null)
    {
    cts.Cancel();
    if(_tcs!=null&&u udpClient!=null)
    _Task.Wait();
    _tokenReg.Dispose();
    cts.Dispose();
    }
    }
    公共空间处置()
    {
    停止();
    如果(_udpClient!=null)
    {
    ((IDisposable)u udpClient.Dispose();
    _udpClient=null;
    }
    总干事(本);
    }
    私人无效开始接收()
    {
    var iar=\u udpClient.Begin