2014-10-11 1 views
1

Starksoft.Net.Ftp을 사용하여 업로드에 대한 비동기 작업을 수행하고 있습니다.왜 두 개의 ManualResetEvent로 작업하면 교착 상태가 발생합니까?

그렇게 보이는 :

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 -method 만 호출하면 잘 작동합니다. 난 둘 다 ResetEvents 같은 리소스 또는 무언가를 사용하여 자신을 중복, 문제는 것 같아요, 나도 몰라.

도움 주셔서 감사합니다.

+0

업로드가 삭제하려는 디렉토리에 잠금을 설정할 수 있습니까? – jaywayco

+0

좋은 점은, 내가 거기에서 만들었지 만, "CancelAsync"가 그것을 관리하지 않아야한다는 것입니까? –

+0

cancelasync가 uploadfinished 이벤트를 트리거합니까? 업로드 클라이언트가 닫히지 않고 리소스에 걸려있을 수없는 경우 – jaywayco

답변

3

이 라이브러리를 올바르게 사용하고 있지 않습니다. 추가 한 MRE로 인해 교착 상태가 발생합니다. _uploadResetEvent.WaitOne()으로 시작하여 UI 스레드를 차단합니다. 이것은 일반적으로 불법입니다. CLR은 메시지 루프 자체를 펌핑하여 UI가 완전히 작동하지 않도록합니다. 그것이 처럼 보입니다. 아직 살아있는 것처럼처럼, 여전히 예를 들어 다시 칠해집니다. 거의 위험하지는 않지만 DoEvents()와 대충 같습니다.

하지만 가장 큰 문제는 PutFileAsyncCompleted 이벤트 처리기를 실행할 수 없으며 기본 비동기 작업자는 일반 BackgroundWorker입니다. 이벤트를 시작한 동일한 스레드에서 이벤트를 시작합니다. 이는 매우 좋습니다. 그러나 UI 스레드가 유휴 상태가 될 때까지 RunWorkerCompleted 이벤트 핸들러를 호출 할 수 없습니다. 어떤 스레드가 WaitOne() 호출에 걸렸습니다. 지금 디버깅중인 내용에 대해 똑같은 이야기를하면 GetDirListAsyncCompleted 이벤트 처리기를 같은 이유로 실행할 수 없습니다. 그래서 진전을 이루지 못하고 거기서 얼어 붙습니다.

_uploadResetEvent를 완전히 제거하려면 대신 UploadFinished() 메소드를 사용하십시오. 취소 된 경우 e.Cancelled 속성에서 확인할 수 있습니다. 다음 디렉토리를 삭제하는 코드를 시작하십시오. 해당 패턴을 따라 해당 XxxAsyncCompleted 이벤트를 사용하여 다음에 수행 할 작업을 결정하십시오. 전혀 MRE가 필요하지 않습니다.

+0

아 좋아, 그래서 내가주의를 기울일 필요가있는 AsyncCompletedEventArgs. 나는 그것을 시도 할 것이다, 고마워. –

+0

So : 끝내기를 기다려야하기 때문에 업로드를 위해 MRE를 유지했습니다. 하지만 이제 Canceled 속성이 선택된 쿼리에서 UploadFinished 핸들러로 삭제를 이동했습니다. 고마워, 지금은 잘 작동한다! –

1

the source을 보면 FtpClientBackgroundWorker을 사용하여 비동기 작업을 수행합니다. 즉, 완료 이벤트가 작업자가 생성 된 시점에 설정된 SynchronizationContext에 게시됩니다.CancelAsync의 완료로 인해 UI 스레드로 되돌아 갈 것이고, 이는 디렉토리 목록 재설정 이벤트에서 WaitOne을 호출 할 때 차단됩니다. GetDirListAsyncCompleted 이벤트는 UI 메시지 루프에 게시되지만 UI 스레드는 차단되므로 절대로 실행되지 않으며 재설정 이벤트가 설정되지 않습니다.

붐! 교착 상태.

+0

설명 주셔서 감사합니다. 그 문제를 해결할 수있는 것이 있습니까? –

+0

디렉토리 목록 작업에 대한 재설정 이벤트를 제거하고'GetDirListAsyncCompleted' 핸들러에서 디렉토리 삭제 코드를 직접 시작할 수도 있습니다. –

+0

좋은 소리, 나는 그것을 시도하고 그것이 효과가 있는지 말해 줄 것이다. 감사! –