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

时间:2018-01-25 18:09:52

标签: c# asynchronous concurrency queue race-condition

这是否会在此课程中产生竞争条件?在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

2 个答案:

答案 0 :(得分:0)

问:这会造成竞争条件吗? 答:是的,完全

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正是您所需要的。

答案 1 :(得分:-1)

从一些评论中可以看出,多线程代码是一项严肃的业务。最好遵循经过验证的模式,例如:生产者 - 消费者,或者至少使用已建立的同步原语,如监视器或信号量。因为我可能错了。您可以轻松查看自己提出的代码并错过常见的多线程问题,而且我对此并不免疫。我可能会在没有看到你的其他解决方案的情况下获得一些评论。

话虽如此,您提供的几乎所有代码都不是多线程代码。如果我错了,请纠正我,但这一切都在主线程上运行*(好await Task.Wait可以将你的代码放在不同的线程上继续,取决于你的同步上下文,但它会被给予相同的线程上下文)。发生的唯一并行处理来自您为下载文件而创建的任务(您甚至没有包含该代码的那一部分),并且唯一需要并行访问的数据元素是int concurrentDownloads。因此,您可以使用Queue(而不是ConcurrentQueue);另外,我不确定calcBusy的目的是什么,因为它似乎不需要。

您必须担心的一件事是并发访问int concurrentDownloads,我确实认为我看到了一些问题,尽管它们不一定会出现在您的操作系统和芯片组上以及你的构建,虽然如果他们这样做,它可能是间歇性的,你可能没有意识到你的代码已经投入生产。以下是我要做的更改:

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

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

  3. 此外,请确保您实际上正在等待任务并处理任何异常。如果一个任务以一个未处理的异常结束,我相信当任务被垃圾收集时它会被抛出,除非你有东西要抓住它,否则它会使整个过程崩溃。