对于并发下载处理程序,这会产生竞争条件吗?

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 上表现出来和芯片组以及您的构建,尽管如果他们这样做可能会断断续续,以至于您可能直到您的代码投入生产后才意识到。以下是我要进行的更改:

  1. concurrentDownloads标记为Volatile。如果不这样做,编译器可能会完全优化变量检查,因为它可能没有意识到它会在其他线程上被修改。此外,CPU 可能会优化主内存访问,导致您的线程具有不同的变量副本。在 Intel 上你是安全的(目前),但如果你 运行 你的代码在某些其他处理器上,它可能无法工作。如果您使用 Volatile,CPU 将获得特殊指令(内存屏障)以确保适当地刷新缓存。

  2. 使用Interlocked.Increment(ref int) and Interlocked.Decrement(ref int)修改计数器。如果不这样做,两个线程可能会同时尝试修改值,如果芯片组不支持原子增量,其中一个修改可能会丢失。

此外,请确保您确实在等待任务并处理任何异常。如果任务以未处理的异常结束,我相信当任务被垃圾收集时它会被抛出,除非你有什么东西可以捕获它,否则它会让你的整个过程崩溃。