将BeginRead循环转换为ReadAsync并按需取消

时间:2014-10-28 12:21:09

标签: c# asynchronous filestream

我尝试构建两个将通过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。

我发现similar有关CancelationToken的问题,但它是用F#编写的,我需要C#。

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

1 个答案:

答案 0 :(得分:0)

转换为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>一起使用,然后在Task<int>完成时将await传递给回调,并使用await进行调用同样。 (您应该只能在Connect()方法中使用一个try / catch块...在continuation中发生的异常应该传播回该try / catch块。

请注意,除了对CallBack()方法声明的更改之外,您还需要更改void Connect()方法声明,使其为async Task Connect()。另请注意,这将强制调用者自己成为async方法并在调用时使用await(依此类推,直到调用链,直到到达UI事件处理程序,可以是{{1}并且使用async,虽然合法地是await方法),或者直接处理返回的void(不推荐,因为这通常意味着否定异步/等待成语的好处) )。

如果你继续使用Task方法,据我所知,取消它的唯一方法是关闭BeginRead()对象(这将导致操作以{{ 1}})。

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