如何使用Task.Factory.FromAsync在Renci.SshNet.BeginDownload中异步/等待

时间:2015-07-02 15:04:12

标签: c# asynchronous task iasyncoperation

我可以访问一个Connected Renci.SshNet.SftpClient,我用它来获取sftp文件夹中的一系列文件。用于此的函数是

Renci.SshNet.SftpClient.ListDirectory(string);

由于目录中存在大量文件,因此大约需要7秒钟。我希望能够使用async / await和cancellationToken保持我的UI响应。

如果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;
}

Alas 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);

在文章Turn IAsyncResult code into the new async and await Pattern中描述了如何将IAsyncResult转换为等待。

但是我不知道如何处理BeginList目录中的所有参数以及放置EndListDirectory的位置。任何人都可以将其转换为一个任务,我可以在短时间内等待检查取消令牌吗?

1 个答案:

答案 0 :(得分:2)

SftpClient看起来不符合标准APM patternlistCallback是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)呼叫。