将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?
转换为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还不够熟悉,无法评论您对异步读取的字节计数结果的使用。但在其他情况下,假设您已经成功读取了最初要求的字节数,那将是一个非常糟糕的主意。通常,您必须保持循环,读取数据,直到您拥有所需的字节数。