如何在Renci.SshNet.BeginDownload中使用async/await和Task.Factory.FromAsync?

3

我可以访问一个已连接的Renci.SshNet.SftpClient,用它来获取SFTP文件夹中文件的序列。此功能的名称为:

Renci.SshNet.SftpClient.ListDirectory(string);

由于目录中的文件数量很多,这需要大约7秒钟的时间。我希望能够使用async/await和cancellationToken使我的用户界面保持响应状态。
如果Renci.SshNet有一个返回任务的ListDirectoryAsync函数,那么这将变得容易起来:
async Task<IEnumerable<SftpFiles> GetFiles(SftpClient connectedSftpClient, CancellationToken token)
{
    var listTask connectedSftpClient.ListDirectoryAsync();
    while (!token.IsCancellatinRequested && !listTask.IsCompleted)
    {
        listTask.Wait(TimeSpan.FromSeconds(0.2);
    }
    token.ThrowIfCancellationRequested();
    return await listTask;
}

遗憾的是,SftpClient没有异步函数。以下代码可以工作,但在下载过程中无法取消:

public async Task<IEnumerable<SftpFile>> GetFilesAsync(string folderName, CancellationToken token)
{
    token.ThrowIfCancellationRequested();
    return await Task.Run(() => GetFiles(folderName), token);
}

然而,SftpClient确实具有使用功能的异步功能。
public IAsyncResult BeginListDirectory(string path, AsyncCallback asyncCallback, object state, Action<int> listCallback = null);
Public IEnumerable<SftpFile> EndListDirectory(IAsyncResult asyncResult);

在文章“将IAsyncResult代码转换为新的async和await模式”中,描述了如何将IAsyncResult转换为await。
然而,我不知道如何处理BeginListdirectory中的所有参数以及放置EndListDirectory的位置。有人能将其转换为可等待的任务,以便我可以使用短时间间隔等待并检查取消标记吗?
1个回答

5
似乎SftpClient没有遵循标准的APM模式listCallback是Begin方法的一个额外参数。因此,我相信你无法使用标准的FromAsync工厂方法。
然而,你可以使用TaskCompletionSource<T>编写自己的异步方法。有点尴尬,但可行的:
public static Task<IEnumerable<SftpFile>> ListDirectoryAsync(this SftpClient @this, string path)
{
  var tcs = new TaskCompletionSource<IEnumerable<SftpFile>>();
  @this.BeginListDirectory(path, asyncResult =>
  {
    try
    {
      tcs.TrySetResult(@this.EndListDirectory(asyncResult));
    }
    catch (OperationCanceledException)
    {
      tcs.TrySetCanceled();
    }
    catch (Exception ex)
    {
      tcs.TrySetException(ex);
    }
  }, null);
  return tcs.Task;
}

(这段代码是在浏览器中编写的,完全没有经过测试 :)

我将其构建为扩展方法,这是我喜欢的方法。这样,您的消费代码可以进行非常自然的connectedSftpClient.ListDirectoryAsync(path)调用。


1
我看到了捕获OperationCancelledException的代码块,但是在调用这个扩展方法时如何加入CancellationToken呢?并没有传递CancellationToken。通常在调用传递CancellationTokenSource.Token的方法后,不是会调用CancellationTokenSource.Cancel()吗? - Adam Venezia
1
@AdamVenezia:BeginListDirectory 方法不接受 CancellationToken,因此 ListDirectoryAsync 也不应该接受。有时候像这样的库会有另一种名为 CancelListDirectory 或者 Cancel 的方法来实现取消操作,在这种情况下,EndListDirectory 可能会抛出 OperationCanceledException 异常。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接