将BeginRead循环转换为ReadAsync并根据需要取消

本文关键字:取消 ReadAsync BeginRead 循环 转换 | 更新日期: 2023-09-27 18:28:03

我试图构建两个将通过RDP相互通信的应用程序。我有基本的沟通,但我想优化代码并删除所有异常。

在服务器上,我打开FileStream,当我从客户端发送东西时,我使用BeginRead:读取它

public void Connect()
{
    mHandle = Native.WTSVirtualChannelOpen(IntPtr.Zero, -1, CHANNEL_NAME);
    Task.Factory.StartNew(() =>
    {
        IntPtr tempPointer = IntPtr.Zero;
        uint pointerLen = 0;
        if (Native.WTSVirtualChannelQuery(mHandle, WtsVirtualClass.WTSVirtualFileHandle, out tempPointer, ref pointerLen))
        {
            IntPtr realPointer = Marshal.ReadIntPtr(tempPointer);
            Native.WTSFreeMemory(tempPointer);
            var fileHandle = new SafeFileHandle(realPointer, false);
            fileStream = new FileStream(fileHandle, FileAccess.ReadWrite, 0x640, true);
            var os = new ObjectState();
            os.RDPStream = fileStream;
            fileStream.BeginRead(os.Buffer, 0, 8, CallBack, os);
        }
    });
}

和我的回电:

private void CallBack(IAsyncResult iar)
{
    if (iar.IsCompleted || iar.CompletedSynchronously)
    {
        var os = iar.AsyncState as ObjectState;
        int readed = 0;
        try
        {
            readed = os.RDPStream.EndRead(iar);
        }
        catch (IOException ex)
        {
            MessageBox.Show(ex.Message);
        }
        if (readed != 0)
        {
            switch (os.State)
            {
                case FileDownloadStatus.Length:
                    os.Length = BitConverter.ToInt32(os.Buffer, 0);
                    os.State = FileDownloadStatus.Body;
                    os.RDPStream.BeginRead(os.Buffer, 0, os.Length, CallBack, os);
                    break;
                case FileDownloadStatus.Body:
                    var message = Encoding.UTF8.GetString(os.Buffer, 0, readed);
                    Debug.WriteLine(message);
                    var newos = new ObjectState {RDPStream = os.RDPStream};
                    os.RDPStream.BeginRead(newos.Buffer, 0, 8, CallBack, newos);
                    break;
            }
        }
    }
}

和所需物品:

enum FileDownloadStatus
{
    Length,
    Body,
}
class ObjectState
{
    public int Length = 0;
    public FileDownloadStatus State = FileDownloadStatus.Length;
    public byte[] Buffer = new byte[0x640];
    public FileStream RDPStream = null;
}

不幸的是,当我试图关闭我的应用程序时,我得到了异常System.OperationCanceledException

我想将其转换为ReadAsync,因为这样我就可以使用CancelationToken,但之后我必须升级到.NET 4.5。

我发现了关于CancelationToken的类似问题,但它是用F#写的,我需要C#。

所以我的问题是:
如何停止BeginRead或将此代码转换为.NET 4.5并使用ReadAsync?

将BeginRead循环转换为ReadAsync并根据需要取消

转换为async/await:

fileStream.BeginRead(os.Buffer, 0, 8, CallBack, os);替换为:

    Task<int> task = fileStream.ReadAsync(os.Buffer, 0, 8, cancelToken);
    try
    {
        await task;
        await CallBack(os, task);
    }
    catch (IOException ex)
    {
        // TODO: unpack task exception and display correctly
        MessageBox.Show(ex.Message);
    }

请注意,我假设您有一个新的cancelToken变量,其中包含您的CancelationToken对象。我想您可以将其存储在ObjectState对象中,以便在您想要调用ReadAsync()的地方可以随时使用它。

CallBack()方法的第一部分(在if (readed != 0)语句之前)更改为:

    private async Task CallBack(ObjectState os, Task<int> task)
    {
        if (task.Status != TaskStatus.Canceled)
        {
            int readed = task.Result;

也就是说,用上面的替换if (readed != 0)语句之前的所有代码,并按上面的方式更改方法声明。

将类似的更改应用于CallBack()方法中的代码,您也可以在其中调用BeginRead()。即使用ReadAsync,将await与返回的Task<int>一起使用,然后在await完成时将Task<int>传递给回调,并使用await调用它。(您应该能够在Connect()方法中只有一个try/catch块…在continuation中发生的异常应该传播回那个try/catch区块)。

请注意,除了更改CallBack()方法声明之外,还需要更改void Connect()方法声明,使其成为async Task Connect()。还要注意的是,这将迫使调用方自己成为async方法,并在调用时使用await(依此类推,在调用链的上游,直到您找到一个UI事件处理程序,它可以是async并使用await,同时合法地是void方法),或者直接处理返回的Task(不推荐使用,因为这通常意味着否定异步/等待习惯用法的好处)。

如果继续使用BeginRead()方法,据我所知,取消的唯一方法是关闭FileStream对象(这将导致使用ObjectDisposedException完成操作)。

最后要注意的是:我对RDP I/O还不够熟悉,无法评论您对异步读取的字节计数结果的使用。但在其他情况下,假设您已经成功读取了最初要求的字节数,那将是一个非常糟糕的主意。通常,您必须保持循环,读取数据,直到您拥有所需的字节数。