所以我认为这可能是我在线程和递增全局计数器方法中的一个根本缺陷,但这是我的问题。我有一个来自我迭代的数据库的文件名集合,对于每个文件名,我在顶级文件夹中搜索它。每次迭代我都会搜索并在计数器完成时递增计数器,这样我就可以确定它何时完成。问题是计数器永远不会像文件总数一样高,有时会非常接近,但永远不会达到我的预期。
public class FindRogeRecords
{
private delegate void FindFileCaller(string folder, int uploadedID, string filename);
private Dictionary<int, string> _files;
private List<int> _uploadedIDs;
private int _filesProcessedCounter;
private bool _completed;
public void Run()
{
_files = GetFilesFromDB(); //returns a dictionary of id's and filenames
FindRogueRecords();
}
private void FindRogueRecords()
{
_uploadedIDs = new List<int>();
foreach (KeyValuePair<int, string> pair in _files)
{
var caller = new FindFileCaller(FindFile);
var result = caller.BeginInvoke(txtSource.Text, pair.Key, pair.Value, new AsyncCallback(FindFile_Completed), null);
}
}
private void FindFile(string documentsFolder, int uploadedID, string filename)
{
var docFolders = AppSettings.DocumentFolders;
foreach (string folder in docFolders)
{
string path = Path.Combine(documentsFolder, folder);
var directory = new DirectoryInfo(path);
var files = directory.GetFiles(filename, SearchOption.AllDirectories);
if (files != null && files.Length > 0) return;
}
lock (_uploadedIDs) _uploadedIDs.Add(uploadedID);
}
private void FindFile_Completed(System.IAsyncResult ar)
{
var result = (AsyncResult)ar;
var caller = (FindFileCaller)result.AsyncDelegate;
_filesProcessedCounter++;
_completed = (_files.Count == _filesProcessedCounter); //this never evaluates to true
}
}
答案 0 :(得分:5)
您正在从多个线程访问_filesProcessedCounter
变量而没有任何同步(即使是简单lock()),因此这会导致您的代码中出现Race Condition。
要递增整数变量,您可以使用线程安全的Interlocked.Increment(),但考虑到以下代码行也需要同步:
_completed = (_files.Count == _filesProcessedCounter);
我建议使用锁定对象来覆盖两行并保持代码清晰:
// Add this field to a class fields list
private static readonly object counterLock = new object();
// wrap access to shared variables by lock as shown below
lock (counterLock)
{
_filesProcessedCounter++;
_completed = (_files.Count == _filesProcessedCounter);
}
答案 1 :(得分:3)
这是因为您的程序中存在竞争条件。由于++运算符等于以下代码
c = c + 1; // c++;
你可以看到,它不是原子的。递增值的线程将c存储在寄存器中,将值递增1然后将其写回。当线程现在被推到一边因为另一个线程获得CPU时,c = c + 1的执行可能无法完成。第二个线程执行相同的操作,读取旧的c并将其值递增1。当第一个线程再次获得CPU时,他将覆盖第二个线程写入的数据。
你可以使用锁来确保一次只有一个线程可以访问变量,或者使用像
这样的原子函数Interlocked.Increment(ref c);
以线程方式增加c;
答案 2 :(得分:2)
答案 3 :(得分:0)
我认为应该是:
if (files != null && files.Length > 0) continue;
而不是返回(你不会增加你的计数器...)
答案 4 :(得分:0)
转换
_filesProcessedCounter++;
_completed = (_files.Count == _filesProcessedCounter);
到
_completed = (_files.Count == Interlocked.Increment(ref _filesProcessedCounter));