2010-03-02 77 views
2

我有一張需要循環訪問的url表,需要下載每個文件,更新表並返回結果。我想同時運行多達10次的下載,使正在考慮使用委託如下:異步運行多個委託並等待C#中的響應#

DataTable photos; 
bool scanning = false, 
    complete = false; 
int rowCount = 0; 

public delegate int downloadFileDelegate(); 

public void page_load(){ 

    photos = Database.getData...   

    downloadFileDelegate d = downloadFile; 

    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 
    d.BeginInvoke(downloadFileComplete, d); 

    while(!complete){} 

    //handle results... 

} 

int downloadFile(){ 

    while(scanning){} scanning = true; 

    DataRow r; 

    for (int ii = 0; ii < rowCount; ii++) { 

     r = photos.Rows[ii]; 

     if ((string)r["status"] == "ready"){ 

      r["status"] = "running"; 

      scanning = false; return ii;     

     } 

     if ((string)r["status"] == "running"){ 

      scanning = false; return -2;     

     } 

    } 

    scanning = false; return -1;   

} 

void downloadFileComplete(IAsyncResult ar){ 

    if (ar == null){ return; } 

    downloadFileDelegate d = (downloadFileDelegate)ar.AsyncState; 

    int i = d.EndInvoke(ar); 

    if (i == -1){ complete = true; return; }  


    //download file... 

    //update row 
    DataRow r = photos.Rows[i]; 

    r["status"] = "complete"; 

    //invoke delegate again 
    d.BeginInvoke(downloadFileComplete, d); 

} 

然而,當我運行這個花費的時間是相同的運行5,因爲它確實1.我期待它要花5倍的速度。

任何想法?

+1

作爲一個問題,您的代碼不是線程安全的。 – RichardOD 2010-03-02 18:48:04

回答

4

你看起來正在嘗試使用無鎖同步化(使用while(scanning)檢查我們設置在布爾多線程是非常有用的函數的開始和最後的重置),但所有這些成功的做法是一次只運行一次檢索。

  1. 是否有理由不讓它們同時運行?這看起來就像你的練習的整個觀點。我看不到這個原因,所以我完全失去了scanning標誌(和相關的邏輯)。
  2. 如果你打算採取這種方法,你的布爾標誌需要被聲明爲volatile(否則他們可能讀取緩存,你可以無休止地等待)
  3. 你的數據更新操作(更新在DataRow值) 必須在UI線程上發生。您必須將這些操作包裝在Control.InvokeControl.BeginInvoke調用中,否則您將跨越線程邊界與控件進行交互。
  4. BeginInvoke返回AsyncWaitHandle。使用此邏輯將在操作完成時執行全部時發生。這樣

東西 -

WaitHandle[] handles = new WaitHandle[] 
{ 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...), 
    d.BeginInvoke(...) 
} 

WaitHandle.WaitAll(handles); 

這將導致調用線程阻塞,直到所有操作完成。

+0

是的,它似乎代碼試圖序列化下載操作。 – 2010-03-02 18:49:27

+0

我指出你指的是while(掃描),而不是while(!complete),因爲最初我對你指的是哪一方面感到困惑。 – RichardOD 2010-03-02 18:55:09

+0

爲了使代碼被寫入同時運行,需要保護對DataTable的訪問。或者,異步邏輯需要從共享數據結構的更新中分解出來。 – LBushkin 2010-03-02 18:56:21

0

如果受到網絡帶寬的限制,它將花費相同的時間。如果你從同一個網站下載所有10個文件,那麼它不會更快。當你要麼需要用戶界面響應,或者您有什麼處理器密集型和多內核

+2

在某些情況下,延遲最終會限制單個連接的吞吐量,並且多次下載最終可能會更快。 – Yuliy 2010-03-02 18:48:02

+0

我同意Yuliy-我會將「不會更快」改寫爲「可能不會更快」 – RichardOD 2010-03-02 18:49:03

0
WaitHandle[] handles = new WaitHandle[5]; 
handles[0] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[1] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[2] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[3] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
handles[4] = d.BeginInvoke(downloadFileComplete, d).AsyncWaitHandle; 
WaitHandle.WaitAll(handles); 
+1

這是一種更好的技術,但無法提高性能。 – RichardOD 2010-03-02 18:47:46

+0

這沒有解決實際導致代碼錯誤執行的問題,而不是對共享數據的不當併發訪問。 – LBushkin 2010-03-02 18:51:46

0

在這個實現中有許多事情並不安全的併發使用。

但是可能導致您所描述的效果的一個事實是downloadFile()有一個while()循環檢查scanning變量。該變量由正在運行的委託的所有實例共享。此while循環可防止委託同時運行。

這種「掃描循環」並不是一個適當的螺紋結構 - 可能的是兩個線程同時讀取變量和兩個設置同時。您應該使用Semaphorelock()語句來保護DataTable免受併發訪問。由於每個線程花費大部分時間等待scanning爲假,downloadFile方法不能同時運行

您應該重新考慮您如何構建此代碼,以便下載數據和更新應用程序中的結構不存在爭用。

+0

在這種情況下,似乎「掃描循環」是完全不必要的,除非省略了某些內容。 – 2010-03-02 18:59:05

+0

@Adam Robinson:掃描循環的目的似乎是保護DataTable免受併發訪問。但我可能是錯的 - 代碼的結構使得很難推斷意圖。 – LBushkin 2010-03-02 19:06:24

0

這樣的事情會更好,我認爲。

public class PhotoDownload 
{ 
    public ManualResetEvent Complete { get; private set; } 
    public Object RequireData { get; private set; } 
    public Object Result { get; private set; } 
} 

public void DownloadPhotos() 
{ 
    var photos = new List<PhotoDownload>(); 

    // build photo download list 

    foreach (var photo in photos) 
    { 
     ThreadPool.QueueUserWorkItem(DownloadPhoto, photo); 
    } 

    // wait for the downloads to complete 

    foreach (var photo in photos) 
    { 
     photo.Complete.WaitOne(); 
    } 

    // make sure everything happened correctly 
} 

public void DownloadPhoto(object state) 
{ 
    var photo = state as PhotoDownload; 
    try 
    { 
      // do not access fields in this class 
      // everything should be inside the photo object 
    } 
    finally 
    { 
     photo.Complete.Set(); 
    } 
}