对于并发下载处理程序,这会产生竞争条件吗?
For a concurrent download handler does this create a race condition?
这是否会在 class 中造成竞争条件?在 while 循环中?只允许同时进行 2 次下载。计算工作不应同时进行(时间敏感)。问题是关于这段代码。我知道还有其他方法可以完成此操作,例如使用 ConcurrentQueue。
//class setup
public class FileService : IFileService
{
private static Queue<string> fileNamesQueue = new Queue<string>();
private static int concurentDownloads = 0;
private static bool calcBusy = false;
...
//called by a button click event in UI
public async void Download(string fileName)
{
fileNamesQueue.Enqueue(fileName);
while (fileNamesQueue.Count > 0)
{
await Task.Delay(500);
if (concurentDownloads < 2 && !calcBusy)
DownloadItem();
}
}
//beginning of perform download
public async void DownloadItem()
{
concurentDownloads++;
var fileName = string.Empty;
if (fileNamesQueue.Count > 0)
{
fileNamesQueue.TryDequeue(out fileName);
//perform calc work but ensure they are not concurrent
calcBusy = true;
//do calc work
calcBusy = false;
//create download task
...
//concurentDownloads gets decremented after each download completes in a completed event
问:这会产生竞争条件吗?
A:是的,完全
imagine this
Thread 1 : Download's Task.Delay(500) concurentDownloads = 0
Thread 1 : enter DownloadItem
Thread 2 : Download's Task.Delay(500) concurentDownloads = 0
Thread 3 : Download's Task.Delay(500) concurentDownloads = 0
Computer LAG
Thread 2 : enter DownloadItem
Thread 3 : enter DownloadItem
Thread 1 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 2 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 3 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 1 : Download Start
Thread 2 : Download Start
Thread 3 : Download Start
.... //concurentDownloads = 1
Thread 1 : Download Finish
Thread 2 : Download Finish
Thread 3 : Download Finish
下载 x 3
你不需要为这样的东西重新发明轮子。 Semaphore 正是您所需要的。
从一些评论中可以看出,多线程代码是一项严肃的工作。最好遵循经过验证的模式,例如生产者-消费者,或者至少使用已建立的同步原语,如监视器或信号量。因为我可能是错的。仔细检查您自己编写的代码并错过常见的多线程问题很容易,我也不能幸免。我可能会因为评论而没有看到您的其余解决方案而得到一些反对票。
也就是说,您提供的几乎所有代码都不是多线程代码。如果我错了,请纠正我,但是主线程上的所有 运行s*(好吧 await Task.Wait
可以将您的代码放在不同的线程上以继续执行,具体取决于您的同步上下文,但它'将被赋予相同的线程上下文)。唯一发生的并行处理来自您为下载文件而创建的任务(您甚至没有包含那部分代码),唯一受并行访问影响的数据元素是 int concurrentDownloads
。因此,您可以使用 Queue
(而不是 ConcurrentQueue
);另外,我不确定 calcBusy
的目的是什么,因为它似乎不需要。
你需要担心的一件事是对 int concurrentDownloads
的并发访问,我确实认为我看到了几个问题,尽管它们不一定会在你的 O/S 上表现出来和芯片组以及您的构建,尽管如果他们这样做可能会断断续续,以至于您可能直到您的代码投入生产后才意识到。以下是我要进行的更改:
将concurrentDownloads
标记为Volatile。如果不这样做,编译器可能会完全优化变量检查,因为它可能没有意识到它会在其他线程上被修改。此外,CPU 可能会优化主内存访问,导致您的线程具有不同的变量副本。在 Intel 上你是安全的(目前),但如果你 运行 你的代码在某些其他处理器上,它可能无法工作。如果您使用 Volatile
,CPU 将获得特殊指令(内存屏障)以确保适当地刷新缓存。
使用Interlocked.Increment(ref int)
and Interlocked.Decrement(ref int)
修改计数器。如果不这样做,两个线程可能会同时尝试修改值,如果芯片组不支持原子增量,其中一个修改可能会丢失。
此外,请确保您确实在等待任务并处理任何异常。如果任务以未处理的异常结束,我相信当任务被垃圾收集时它会被抛出,除非你有什么东西可以捕获它,否则它会让你的整个过程崩溃。
这是否会在 class 中造成竞争条件?在 while 循环中?只允许同时进行 2 次下载。计算工作不应同时进行(时间敏感)。问题是关于这段代码。我知道还有其他方法可以完成此操作,例如使用 ConcurrentQueue。
//class setup
public class FileService : IFileService
{
private static Queue<string> fileNamesQueue = new Queue<string>();
private static int concurentDownloads = 0;
private static bool calcBusy = false;
...
//called by a button click event in UI
public async void Download(string fileName)
{
fileNamesQueue.Enqueue(fileName);
while (fileNamesQueue.Count > 0)
{
await Task.Delay(500);
if (concurentDownloads < 2 && !calcBusy)
DownloadItem();
}
}
//beginning of perform download
public async void DownloadItem()
{
concurentDownloads++;
var fileName = string.Empty;
if (fileNamesQueue.Count > 0)
{
fileNamesQueue.TryDequeue(out fileName);
//perform calc work but ensure they are not concurrent
calcBusy = true;
//do calc work
calcBusy = false;
//create download task
...
//concurentDownloads gets decremented after each download completes in a completed event
问:这会产生竞争条件吗?
A:是的,完全
imagine this
Thread 1 : Download's Task.Delay(500) concurentDownloads = 0
Thread 1 : enter DownloadItem
Thread 2 : Download's Task.Delay(500) concurentDownloads = 0
Thread 3 : Download's Task.Delay(500) concurentDownloads = 0
Computer LAG
Thread 2 : enter DownloadItem
Thread 3 : enter DownloadItem
Thread 1 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 2 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 3 : concurentDownloads++ -> concurentDownloads = 0 + 1 = 1
Thread 1 : Download Start
Thread 2 : Download Start
Thread 3 : Download Start
.... //concurentDownloads = 1
Thread 1 : Download Finish
Thread 2 : Download Finish
Thread 3 : Download Finish
下载 x 3
你不需要为这样的东西重新发明轮子。 Semaphore 正是您所需要的。
从一些评论中可以看出,多线程代码是一项严肃的工作。最好遵循经过验证的模式,例如生产者-消费者,或者至少使用已建立的同步原语,如监视器或信号量。因为我可能是错的。仔细检查您自己编写的代码并错过常见的多线程问题很容易,我也不能幸免。我可能会因为评论而没有看到您的其余解决方案而得到一些反对票。
也就是说,您提供的几乎所有代码都不是多线程代码。如果我错了,请纠正我,但是主线程上的所有 运行s*(好吧 await Task.Wait
可以将您的代码放在不同的线程上以继续执行,具体取决于您的同步上下文,但它'将被赋予相同的线程上下文)。唯一发生的并行处理来自您为下载文件而创建的任务(您甚至没有包含那部分代码),唯一受并行访问影响的数据元素是 int concurrentDownloads
。因此,您可以使用 Queue
(而不是 ConcurrentQueue
);另外,我不确定 calcBusy
的目的是什么,因为它似乎不需要。
你需要担心的一件事是对 int concurrentDownloads
的并发访问,我确实认为我看到了几个问题,尽管它们不一定会在你的 O/S 上表现出来和芯片组以及您的构建,尽管如果他们这样做可能会断断续续,以至于您可能直到您的代码投入生产后才意识到。以下是我要进行的更改:
将
concurrentDownloads
标记为Volatile。如果不这样做,编译器可能会完全优化变量检查,因为它可能没有意识到它会在其他线程上被修改。此外,CPU 可能会优化主内存访问,导致您的线程具有不同的变量副本。在 Intel 上你是安全的(目前),但如果你 运行 你的代码在某些其他处理器上,它可能无法工作。如果您使用Volatile
,CPU 将获得特殊指令(内存屏障)以确保适当地刷新缓存。使用
Interlocked.Increment(ref int)
andInterlocked.Decrement(ref int)
修改计数器。如果不这样做,两个线程可能会同时尝试修改值,如果芯片组不支持原子增量,其中一个修改可能会丢失。
此外,请确保您确实在等待任务并处理任何异常。如果任务以未处理的异常结束,我相信当任务被垃圾收集时它会被抛出,除非你有什么东西可以捕获它,否则它会让你的整个过程崩溃。