【问题标题】:How to async / await in Renci.SshNet.BeginDownload with Task.Factory.FromAsync如何使用 Task.Factory.FromAsync 在 Renci.SshNet.BeginDownload 中异步/等待
【发布时间】:2015-07-02 15:04:12
【问题描述】:

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

Renci.SshNet.SftpClient.ListDirectory(string);

由于目录中有大量文件,这大约需要 7 秒。我希望能够使用 async / await 和 cancelToken 保持我的 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;
}

唉,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 转换为 await。

但是我不知道如何处理 BeginListdirectory 中的所有参数以及将 EndListDirectory 放在哪里。任何人都可以将其转换为一个任务,我可以等待很短的超时来检查取消令牌?

【问题讨论】:

    标签: c# asynchronous task iasyncoperation


    【解决方案1】:

    看起来SftpClient 不遵循标准APM patternlistCallback 是 Begin 方法的额外参数。因此,我很确定你不能使用标准的FromAsync 工厂方法。

    但是,您可以使用TaskCompletionSource&lt;T&gt; 编写自己的代码。有点尴尬,但可行:

    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) 类型的调用。

    【讨论】:

    • 我看到了 catch OperationCancelledException 块,但是在调用这个扩展方法时如何合并一个 CancellationToken 呢?没有传入 CancellationToken。通常不是在调用传入 CancellationTokenSource.Token 的方法后调用 CancellationTokenSource.Cancel() 吗?
    • @AdamVenezia:BeginListDirectory 不需要CancellationToken,所以ListDirectoryAsync 也不应该。有时像这样的库有另一种CancelListDirectory 或者只是Cancel 方法可以用来实现取消,在这种情况下EndListDirectory 可能会抛出OperationCanceledException
    猜你喜欢
    • 2020-07-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-15
    • 2021-10-22
    • 2016-02-09
    • 1970-01-01
    相关资源
    最近更新 更多