1

我正在使用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();
    }

由于取消时没有调用上传的完成事件,我需要在DeleteDirectory方法中手动设置ResetEvent。

现在,我在这里做什么:我首先列出目录中的所有文件以便删除它们,因为无法删除已填充的文件夹。

此方法GetDirListAsync也是异步的,这意味着我需要另一个ManualResetEvent,因为我不希望表单冻结。

此 ResetEvent 是_directoryFilesListingResetEvent。它像上面的_uploadResetEvent一样声明。

现在,问题是,它转到_directoryFilesListingResetEvent的 WaitOne 调用,然后卡住了。一个死锁并且表单冻结。(我也在代码中标记了它)

这是为什么?我试图移动_uploadResetEvent.Set()的调用的调用,但它没有改变。有没有人看到问题?

当我尝试调用DeleteDirectory -方法而不进行任何上传时,它也可以正常工作。我认为问题在于两个 ResetEvents 都使用相同的资源或其他东西并且自己重叠,我不知道。

谢谢你的帮助。

4

2 回答 2

3

您没有正确使用此库。您添加的 MRE 会导致死锁。从 _uploadResetEvent.WaitOne() 开始,阻塞了 UI 线程。这通常是非法的,CLR 确保您的 UI 不会通过自己发送消息循环而完全死掉。这使它看起来像还活着,例如它仍然重新绘制。大致相当于 DoEvents(),虽然没有那么危险。

但它最大的问题是它不允许你的 PutFileAsyncCompleted 事件处理程序运行,底层的异步工作者是一个普通的 BackgroundWorker。它在启动它的同一线程上触发其事件,这非常好。但在 UI 线程空闲之前,它无法调用其 RunWorkerCompleted 事件处理程序。这不好,线程卡在 WaitOne() 调用中。与您现在正在调试的内容完全相同,您的 GetDirListAsyncCompleted 事件处理程序由于相同的原因无法运行。所以它只是冻结在那里而无法取得进展。

所以完全消除 _uploadResetEvent ,改用你的 UploadFinished() 方法。您可以查看是否已从e.Cancelled酒店取消。只有这样你才开始删除目录的代码。遵循相同的模式,使用相应的 XxxAsyncCompleted 事件来决定下一步做什么。根本不需要 MRE。

于 2014-10-17T20:23:03.437 回答
1

查看源代码,它似乎FtpClient使用 aBackgroundWorker来执行异步操作。这意味着它的完成事件将被发布到SynchronizationContext创建工人时设置的任何内容。我敢打赌,完成后CancelAsync会将您推回到 UI 线程,当您调用WaitOne目录列表重置事件时,该线程会阻塞。该GetDirListAsyncCompleted事件被发布到 UI 消息循环,但由于 UI 线程被阻塞,它永远不会运行,并且永远不会设置重置事件。

繁荣! 僵局。

于 2014-10-17T19:59:46.500 回答