这是否会在此课程中产生竞争条件?在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
答案 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
,我确实认为我看到了一些问题,尽管它们不一定会出现在您的操作系统和芯片组上以及你的构建,虽然如果他们这样做,它可能是间歇性的,你可能没有意识到你的代码已经投入生产。以下是我要做的更改:
将concurrentDownloads
标记为Volatile。如果不这样做,编译器可能会完全优化变量检查,因为它可能没有意识到它将在其他线程上被修改。此外,CPU可能会优化主内存访问,导致您的线程具有不同的变量副本。在英特尔,你是安全的(现在),但如果你在某些其他处理器上运行你的代码,它可能无法正常工作。如果使用Volatile
,将向CPU提供特殊指令(内存屏障)以确保正确刷新缓存。
使用Interlocked.Increment(ref int)
and Interlocked.Decrement(ref int)
修改计数器。如果不这样做,两个线程可能会同时尝试修改该值,如果芯片组不支持原子增量,则其中一个修改可能会丢失。
此外,请确保您实际上正在等待任务并处理任何异常。如果一个任务以一个未处理的异常结束,我相信当任务被垃圾收集时它会被抛出,除非你有东西要抓住它,否则它会使整个过程崩溃。