2013-04-29 175 views
0

如何控制對Parallel.ForEach循環中共享資源的訪問?我試圖並行下載多個文件,並且想要捕獲有關下載失敗的信息,以便用戶稍後可以重新嘗試下載。但是,我擔心如果多個下載同時失敗,應用程序將拋出一個異常,因爲一個線程將在另一個線程正在寫入時嘗試訪問該文件。Parallel.ForEach中的共享資源

在下面的代碼中,我想知道如何控制對RepeateRequestPath文件的訪問。 RequestSet是表示我嘗試下載的資源ID的字符串列表。

Dim DownloadCnt As Integer = 0 
Dim ParallelOpts As New ParallelOptions() 
ParallelOpts.MaxDegreeOfParallelism = 4 
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet) 
     Try 
      DownloadCnt += 1 
      Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract") 
      DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt)) 
     Catch ex As WebException 
      Using Response As WebResponse = ex.Response 
       Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode) 
       MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode)) 
       If Not File.Exists(RepeatRequestPath) Then 
        File.WriteAllLines(RepeatRequestPath, RequestSet) 
       Else 
        File.AppendAllLines(RepeatRequestPath, RequestSet) 
       End If 
      End Using 
     End Try 
    End Sub) 

回答

2

,以保護在VB.NET共享資源的常用方法是使用SyncLock

所以,你會在Parallel.ForEach()循環之前創建的鎖定對象:

Dim lock = New Object 

,然後你會使用內循環:

SyncLock lock 
    File.AppendAllLines(RepeatRequestPath, RequestSet) 
End SyncLock 

另外請注意,您可以使用AppendAllLines()即使該文件尚不存在,所以你不必檢查。

+0

完美!快速的問題,但。我可以在Try-Catch塊的Try部分的不同文件上使用相同的鎖嗎?或者我必須實例化一個單獨的對象?我想在調用DownloadFile之後對另一個文件做同樣的事情。 – user667118 2013-04-29 20:54:10

+0

@ user667118你可以。另一個問題是你是否應該那樣做,因爲這意味着如果一個線程想要寫入一個文件,它必須等待另一個寫入另一個文件的線程。 – svick 2013-04-29 21:06:18

+0

我認爲這不會有太大問題,因爲寫入文件不需要很長時間。總體而言,我仍然節省了大量時間,因爲此代碼以前以同步方式運行。 – user667118 2013-04-29 21:20:17

1

您需要使用信號量來控制對共享資源的訪問。你只需要一個線程一次訪問錯誤文件,所以初始化信號量只允許1個線程進入。調用_pool.WaitOne應該捕獲信號量,並在完成創建/寫入文件後釋放信號量。

Private Shared _pool As Semaphore 
_pool = = New Semaphore(0, 1) 

Dim DownloadCnt As Integer = 0 
Dim ParallelOpts As New ParallelOptions() 
ParallelOpts.MaxDegreeOfParallelism = 4 
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet) 
     Try 
      DownloadCnt += 1 
      Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract") 
      DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt)) 
     Catch ex As WebException 
      Using Response As WebResponse = ex.Response 
       Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode) 
       MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode)) 
       _pool.WaitOne() 
       Try 
        If Not File.Exists(RepeatRequestPath) Then 
         File.WriteAllLines(RepeatRequestPath, RequestSet) 
        Else 
         File.AppendAllLines(RepeatRequestPath, RequestSet) 
        End If 
       Catch ex as Exception 
        'Do some error handling here. 
       Finally 
        _pool.Release() 
       End Try 
      End Using 
     End Try 
    End Sub) 
+0

好點 - 發佈不會被調用。不確定try catch塊在catch塊中的可接受程度。 – 2013-04-29 20:32:06

+0

我選擇了svick的解決方案,因爲它更短,但我很感激幫助! – user667118 2013-04-29 20:51:46

0

svick的解決方案几乎是正確的。但是,如果您需要保護對共享變量的訪問權限,則還需要將您的鎖定對象聲明爲在課程級別共享。

這正常工作:

Friend Class SomeClass 
    Private Shared _lock As New Object 

    Private Shared sharedInt As Integer = 0 

    Sub Main() 
     SyncLock _lock 
      sharedInt += 1 
     End SyncLock 
    End Sub 
End Class 

如果使用非共享的鎖定對象,爲SyncLock將保護該變量只從多個線程訪問同一個實例中,不能跨實例。