2013-03-23 82 views
1

爲了示例的目的,我有一個列表視圖,其中有3個項目。每個項目包含一個文件夾路徑列和一個文件夾中文件數量列的列。瞭解使用多個backgroundworkers

如果我啓動一個單獨的backgroundworker來計算每個文件夾中的文件,我會得到意外的結果。我想我已經追蹤了這個問題,但我不知道如何解決這個問題。

在我下面的例子中,我用兩種不同的方法計算了每個文件夾中的文件;第一種方法爲每個文件夾創建一個背景工作器,每個背景工作器在計數文件時同時運行。第二種方法創建一個後臺工作人員對每個文件夾中的文件進行串聯計數。串聯計數確實有效,而同時計數則沒有。

這個問題似乎是在法GetPicturesConcurrently(),特別是在讀取行:

fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(item.Text)); 

什麼似乎是發生的是,實際上被傳遞到CountFilesInFolder的每次調用(字符串字符串)最終會使用創建的最後一個backgroundworker中的字符串到達​​該方法;就好像來自item.Text的字符串是通過引用而不是按值傳遞一樣。所以我最終一次又一次地對同一文件夾中的文件進行計數。

當我在創建backgroundworker的時候中斷時,我可以看到每次都傳遞正確的字符串;當我在CountFilesInFolder上中斷時,輸入的最後一個字符串會在每次調用時得到處理。

下面是一個說明問題的例子:

public partial class Form1 : Form 
{ 
    private ConcurrentDictionary<string, int> MyFiles; 
    private List<string> Folders; 

    public Form1() 
    { 
     MyFiles = new ConcurrentDictionary<string,int>(); 
     Folders = new List<string>(); 

     InitializeComponent(); 
     PopulateListview(); 
    } 

    private void PopulateListview() 
    { 
     ListViewItem item1 = new ListViewItem(); 
     ListViewItem item2 = new ListViewItem(); 
     ListViewItem item3 = new ListViewItem(); 

     item1.Text = @"V:\"; 
     item2.Text = @"D:\"; 
     item3.Text = @"C:\"; 

     item1.SubItems.Add(""); 
     item2.SubItems.Add(""); 
     item3.SubItems.Add(""); 

     listView1.Items.Add(item1); 
     listView1.Items.Add(item2); 
     listView1.Items.Add(item3); 
    } 



    private void GetPicturesInSeries() 
    { 
     Reset(); 

     foreach (ListViewItem item in listView1.Items) 
     { 
      Folders.Add(item.Text); 
     } 

     BackgroundWorker fileCounter = new BackgroundWorker(); 
     fileCounter.DoWork += new DoWorkEventHandler((obj, e) => GetPictures()); 
     fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView()); 
     fileCounter.RunWorkerAsync();    
    }   

    private void GetPicturesConcurrently() 
    { 
     Reset(); 


     foreach (ListViewItem item in listView1.Items) 
     { 
      BackgroundWorker fileCounter = new BackgroundWorker(); 
      fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(item.Text)); 
      fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView(item.Index)); 
      fileCounter.RunWorkerAsync();    
     } 

    } 

    private void GetPictures() 
    { 
     foreach (string folder in Folders) 
     { 
      CountFilesInFolder(folder); 
     } 
    } 

    private void CountFilesInFolder(string folder) 
    { 
     DirectoryInfo dirInfo = new DirectoryInfo(folder); 

     IEnumerable<FileInfo> files = dirInfo.EnumerateFiles(); 

     int count = files.Count(); 

     MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => files.Count()); 

    } 

    private void UpdateCountListView(int index) 
    { 
     string key = listView1.Items[index].Text; 

     int count; 
     MyFiles.TryGetValue(key,out count); 

     listView1.BeginUpdate(); 
     listView1.Items[index].SubItems[1].Text = count.ToString(); 
     listView1.EndUpdate(); 
     listView1.Refresh(); 
    } 

    private void UpdateCountListView() 
    { 
     listView1.BeginUpdate(); 

     foreach (ListViewItem item in listView1.Items) 
     { 
      string key = item.Text; 

      int count; 
      MyFiles.TryGetValue(key, out count); 

      listView1.Items[item.Index].SubItems[1].Text = count.ToString(); 
     } 

     listView1.EndUpdate(); 
     listView1.Refresh(); 
    } 

    private void Reset() 
    { 
     listView1.BeginUpdate(); 
     foreach (ListViewItem item in listView1.Items) 
     { 
      item.SubItems[1].Text = ""; 
     } 
     listView1.EndUpdate(); 
     listView1.Refresh(); 


     Folders.Clear(); 
     MyFiles.Clear(); 
    } 
} 

回答

1

我認爲你可能會被修改GetPicturesConcurrently()捕獲的變量,以便改變它在使用前把這些變量的副本,如這樣的:

private void GetPicturesConcurrently() 
{ 
    Reset(); 

    foreach (ListViewItem item in listView1.Items) 
    { 
     var copy = item; 
     BackgroundWorker fileCounter = new BackgroundWorker(); 
     fileCounter.DoWork += new DoWorkEventHandler((obj, e) => CountFilesInFolder(copy.Text)); 
     fileCounter.RunWorkerCompleted += new RunWorkerCompletedEventHandler((obj, e) => UpdateCountListView(copy.Index)); 
     fileCounter.RunWorkerAsync();    
    } 
} 

其次,你CountFilesInFolder()可能被兩次列舉的所有文件:

private void CountFilesInFolder(string folder) 
{ 
    DirectoryInfo dirInfo = new DirectoryInfo(folder); 

    IEnumerable<FileInfo> files = dirInfo.EnumerateFiles(); 

    int count = files.Count(); 

    MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => files.Count()); 
} 

如果folder已經在MyFiles當您撥打AddOrUpdate那麼它會再次呼叫files.Count() - 這將再次枚舉所有文件!

如果這是不可能的folder已經在MyFiles然後就打電話MyFiles.Add()代替MyFiles.AddOrUpdate()

如果可能folder已經在MyFiles然後將其更改爲:

MyFiles.AddOrUpdate(folder, count, (key, oldvalue) => count);

+0

非常感謝!我從來沒有聽說過捕獲的變量,但我現在正在閱讀它們。使用捕獲的變量進行建議更改可以解決問題。 只要有關枚舉所有文件的可能性的評論兩次去;集合MyFiles是一個ConcurrentDictionary,所以沒有Add()函數。我認爲我的選項是AddOrUpdate()或TryAdd()。但是我明白了你對枚舉事物兩次的看法,所以我一定會拿出你寶貴的建議,並改變它來計數而不是files.count()。 再次感謝! – Blau 2013-03-23 13:31:06

+0

啊是的,ConcurrentDictionary隱藏了Add()方法,我忘記了這一點。 – 2013-03-23 13:35:28