Я выполняю асинхронную операцию загрузки с помощью Starksoft.Net.Ftp.
Выглядит так:
public void UploadFile(string filePath, string packageVersion)
{
_uploadFtpClient= new FtpClient(Host, Port, FtpSecurityProtocol.None)
{
DataTransferMode = UsePassiveMode ? TransferMode.Passive : TransferMode.Active,
FileTransferType = TransferType.Binary,
};
_uploadFtpClient.TransferProgress += TransferProgressChangedEventHandler;
_uploadFtpClient.PutFileAsyncCompleted += UploadFinished;
_uploadFtpClient.Open(Username, Password);
_uploadFtpClient.ChangeDirectoryMultiPath(Directory);
_uploadFtpClient.MakeDirectory(newDirectory);
_uploadFtpClient.ChangeDirectory(newDirectory);
_uploadFtpClient.PutFileAsync(filePath, FileAction.Create);
_uploadResetEvent.WaitOne();
_uploadFtpClient.Close();
}
private void UploadFinished(object sender, PutFileAsyncCompletedEventArgs e)
{
if (e.Error != null)
{
if (e.Error.InnerException != null)
UploadException = e.Error.InnerException;
}
_uploadResetEvent.Set();
}
Как видите, здесь есть ManualResetEvent, объявленный как закрытая переменная поверх класса:
private ManualResetEvent _uploadResetEvent = new ManualResetEvent(false);
Ну смысл как раз в том, что он должен дождаться завершения загрузки, но он должен быть асинхронным для отчета о прогрессе, вот и все.
Теперь это просто отлично работает. У меня есть второй метод, который должен отменить загрузку, если это необходимо.
public void Cancel()
{
_uploadFtpClient.CancelAsync();
}
При отмене загрузки директория на сервере также должна быть удалена. У меня тоже есть метод для этого:
public void DeleteDirectory(string directoryName)
{
_uploadResetEvent.Set(); // As the finished event of the upload is not called when cancelling, I need to set the ResetEvent manually here.
if (!_hasAlreadyFixedStrings)
FixProperties();
var directoryEmptyingClient = new FtpClient(Host, Port, FtpSecurityProtocol.None)
{
DataTransferMode = UsePassiveMode ? TransferMode.Passive : TransferMode.Active,
FileTransferType = TransferType.Binary
};
directoryEmptyingClient.Open(Username, Password);
directoryEmptyingClient.ChangeDirectoryMultiPath(String.Format("/{0}/{1}", Directory, directoryName));
directoryEmptyingClient.GetDirListAsyncCompleted += DirectoryListingFinished;
directoryEmptyingClient.GetDirListAsync();
_directoryFilesListingResetEvent.WaitOne(); // Deadlock appears here
if (_directoryCollection != null)
{
foreach (FtpItem directoryItem in _directoryCollection)
{
directoryEmptyingClient.DeleteFile(directoryItem.Name);
}
}
directoryEmptyingClient.Close();
var directoryDeletingClient = new FtpClient(Host, Port, FtpSecurityProtocol.None)
{
DataTransferMode = UsePassiveMode ? TransferMode.Passive : TransferMode.Active,
FileTransferType = TransferType.Binary
};
directoryDeletingClient.Open(Username, Password);
directoryDeletingClient.ChangeDirectoryMultiPath(Directory);
directoryDeletingClient.DeleteDirectory(directoryName);
directoryDeletingClient.Close();
}
private void DirectoryListingFinished(object sender, GetDirListAsyncCompletedEventArgs e)
{
_directoryCollection = e.DirectoryListingResult;
_directoryFilesListingResetEvent.Set();
}
Поскольку событие завершения загрузки не вызывается при отмене, мне нужно установить ResetEvent вручную в методе DeleteDirectory.
Теперь, что я здесь делаю: я сначала перечисляю все файлы в каталоге, чтобы удалить их, так как заполненная папка не может быть удалена.
Этот метод GetDirListAsync также является асинхронным, что означает, что мне нужно еще одно ManualResetEvent, так как я не хочу, чтобы форма зависала.
Это ResetEvent — _directoryFilesListingResetEvent. Оно объявляется аналогично _uploadResetEvent выше.
Теперь проблема в том, что он переходит к вызову WaitOne события _directoryFilesListingResetEvent, а затем зависает. Появляется взаимная блокировка, и форма зависает. (я также отметил это в коде)
Почему это? Я попытался переместить вызов _uploadResetEvent.Set(), но ничего не изменилось. Кто-нибудь видит проблему?
Когда я пытаюсь вызвать метод DeleteDirectory без какой-либо загрузки, он также работает. Я думаю, проблема в том, что оба ResetEvents используют один и тот же ресурс или что-то в этом роде и перекрывают друг друга, я не знаю.
Спасибо за вашу помощь.