它是安全调用的BeginXXX无需调用EndXXX如果实例已处置 [英] Is it safe to call BeginXXX without calling EndXXX if the instance is already disposed

查看:131
本文介绍了它是安全调用的BeginXXX无需调用EndXXX如果实例已处置的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在使用异步编程模型,通常建议以匹配每个的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 例如您选择,它正好的情况是:


  1. 电话 EndXXX 处置的 UDPClient 实例会导致它直接抛出的ObjectDisposedException

  2. 无资源配置中或作为 EndXXX 调用的结果。

  3. 的资源捆绑与此操作(原生重叠和牵制非托管缓存),将在异步操作完成回调被回收。

因此​​,在这种情况下,它是非常安全的,无泄漏

作为一个一般的方法

我不相信这种方式是作为一般的做法正确,因为:


  1. 的实施可能会改变未来,打破你的假设。

  2. 有更好的方法来做到这一点,使用注销和超时你的异步(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 an EndXXX, 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 calling Dispose?

If for example I use UdpClient.BeginReceive in a UdpListener:

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 an ObjectDisposedException)?


Edit:

It isn't uncommon to dispose a UdpClient (and other IDisposables) 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 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?

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 the EndXXX 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:

  1. Calling EndXXX after disposing the UDPClient instance will result in it directly throwing an ObjectDisposedException.
  2. No resources are disposed in or as a result of the EndXXX call.
  3. 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:

  1. The implementation could change in the future, breaking your assumptions.
  2. 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 the EndReceive 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 the EndReceive method. Typically, the method is invoked by the callback delegate.

To cancel a pending BeginReceive, call the Close 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆