Как выполнить асинхронизацию / ожидание в Renci.SshNet.BeginDownload с помощью Task.Factory.FromAsync

У меня есть доступ к Connected 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 в новый асинхронный и ожидающий шаблон описано, как преобразовать IAsyncResult в ожидающий.

Однако я понятия не имею, что делать со всеми параметрами в BeginListdirectory и куда поместить EndListDirectory. Кто-нибудь может преобразовать это в Задание, которое я могу ждать с короткими тайм-аутами, чтобы проверить токен отмены?

1 ответ

Похоже, 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) вид звонка.

Другие вопросы по тегам