线程化和递增
本文关键字:线程 | 更新日期: 2023-09-27 18:16:43
所以我认为这可能是我线程化和增加全局计数器的方法中的一个根本缺陷,但这是我的问题。我有一个来自DB的文件名集合,我要遍历它,对于每个文件名,我在一个顶级文件夹中搜索它。每次迭代我都执行搜索,并在搜索完成时增加一个计数器,这样我就可以确定搜索何时结束。问题是计数器永远不会得到与总文件数一样高的值,它有时非常接近,但永远不会达到我所期望的值。
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
}
}
你从多个线程访问_filesProcessedCounter
变量,没有任何同步(甚至简单的lock()),所以这导致了你的代码中的竞态。
要增加一个整数变量,可以使用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);
}
这是因为程序中存在竞争条件。因为++操作符等于下面的代码
c = c + 1; // c++;
你可以看到,它不是原子的。增加值的线程将c存储在寄存器中,将值增加1,然后将其写回。当线程现在被推到一边,因为另一个线程得到了CPU, c = c + 1的执行可能还没有完成。第二个线程执行相同的操作,读取旧的c并将其值加1。当第一个线程再次获得CPU时,它将覆盖第二个线程所写的数据。
您可以使用锁来确保一次只有一个线程可以访问该变量,或者使用原子函数,如
Interlocked.Increment(ref c);
使用Interlocked.Increment(ref _filesProcessedCounter)
我想应该是:
if (files != null && files.Length > 0) continue;
转换
_filesProcessedCounter++;
_completed = (_files.Count == _filesProcessedCounter);
_completed = (_files.Count == Interlocked.Increment(ref _filesProcessedCounter));