我不确定的情况涉及使用"线程安全" PipeStream,其中多个线程可以添加要写入的消息。如果没有要写入的消息队列,则当前线程将开始写入读取方。如果有一个队列,并且在管道写入时队列增长,我希望开始写入的线程耗尽队列。
我"希望"这种设计(如下所示)阻止了SemaphoreSlim的连续进入/释放并减少了预定的任务数量。我说"希望"因为我应该测试这种并发症是否有任何积极的性能影响。但是,在测试之前我应该首先了解代码是否符合我的想法,所以请考虑以下类,并在其下面考虑一系列事件;
注意:我理解任务的执行与任何特定线程无关,但我发现这是解释的最简单方法。
class SemaphoreExample
{
// Wrapper around a NamedPipeClientStream
private readonly MessagePipeClient m_pipe =
new MessagePipeClient("somePipe");
private readonly SemaphoreSlim m_semaphore =
new SemaphoreSlim(1, 1);
private readonly BlockingCollection<Message> m_messages =
new BlockingCollection<Message>(new ConcurrentQueue<Message>());
public Task Send<T>(T content)
where T : class
{
if (!this.m_messages.TryAdd(new Message<T>(content)))
throw new InvalidOperationException("No more requests!");
Task dequeue = TryDequeue();
return Task.FromResult(true);
// In reality this class (and method) is more complex.
// There is a similiar pipe (and wrkr) in the other direction.
// The "sent jobs" is kept in a dictionary and this method
// returns a task belonging to a completionsource tied
// to the "sent job". The wrkr responsible for the other
// pipe reads a response and sets the corresponding
// completionsource.
}
private async Task TryDequeue()
{
if (!this.m_semaphore.Wait(0))
return; // someone else is already here
try
{
Message message;
while (this.m_messages.TryTake(out message))
{
await this.m_pipe.WriteAsync(message);
}
}
finally { this.m_semaphore.Release(); }
}
}
这一系列活动是否可行?我是否应该完全忘记这个想法,并且每次都要打电话给&#34;发送&#34;等待#34; TryDeque&#34;和它内的信号量?也许每个方法调用安排另一个任务的潜在性能影响可以忽略不计,即使是在一个&#34;高&#34;频率。
更新
按照 Alex 的建议,我正在做以下事情; 让&#34;发送&#34;的来电者指定&#34; maxWorkload&#34;一个整数,指定在将工作委托给另一个线程来处理任何额外工作之前调用者准备做多少项(对于其他调用者,在最坏的情况下)。但是,在创建新主题之前,其他来电者是&#34;发送&#34;有机会进入信号量,从而可能阻止使用额外的线程。
为了不让任何工作留在队列中,任何成功进入信号量并做了一些工作的工作人员必须检查退出信号量后是否添加了新工作。如果这是真的,那么同一个工人将尝试重新输入(如果&#34; maxWorkload&#34;未达到)或委托上述工作。
以下示例:现在发送设置&#34; TryPool&#34;作为&#34; TryDequeue&#34;的延续。 &#34; TryPool&#34;只有在&#34; TryDequeue&#34;返回true(即在输入信号量时做了一些工作)。
// maxWorkload cannot be -1 for this method
private async Task<bool> TryDequeue(int maxWorkload)
{
int currWorkload = 0;
while (this.m_messages.Count != 0 && this.m_semaphore.Wait(0))
{
try
{
currWorkload = await Dequeue(currWorkload, maxWorkload);
if (currWorkload >= maxWorkload)
return true;
}
finally
{
this.m_semaphore.Release();
}
}
return false;
}
private Task TryPool()
{
if (this.m_messages.Count == 0 || !this.m_semaphore.Wait(0))
return Task<bool>.FromResult(false);
return Task.Run(async () =>
{
do
{
try
{
await Dequeue(0, -1);
}
finally
{
this.m_semaphore.Release();
}
}
while (this.m_messages.Count != 0 && this.m_semaphore.Wait(0));
});
}
private async Task<int> Dequeue(int currWorkload, int maxWorkload)
{
while (currWorkload < maxWorkload || maxWorkload == -1)
{
Message message;
if (!this.m_messages.TryTake(out message))
return currWorkload;
await this.m_pipe.WriteAsync(message);
currWorkload++;
}
return maxWorkload;
}
答案 0 :(得分:2)
我倾向于将这种模式称为“GatedBatchWriter”,即通过门的第一个线程处理一批任务;它自己和其他一些代表其他作家,直到它做了足够的工作。
由于与该工作相关的开销,当批处理工作更有效时,此模式主要是有用的。例如。将较大的块一次写入磁盘而不是多个小块。
是的,这个特定的模式有一个特定的竞争条件需要注意:“负责的作家”,即通过大门的那个,确定队列中没有更多的消息并在释放信号量之前停止(即它的写作责任)。第二位作家到达并在这两个决策点之间未能获得写作责任。现在队列中有一条消息将不会被传递(或者在下一位作者到达时传递得很晚)。
此外,就调度而言,您现在正在做的事情并不公平。如果有很多消息,那么队列可能永远不会是空的,并且通过门的作者将忙于代表其他人永久地写消息。您需要限制负责编写者的批量大小。
您可能想要改变的其他一些事情是:
Message
包含任务完成令牌。还有一点需要注意:如果有很多消息,即平均高消息负载,处理队列的专用线程/长时间运行任务通常会有更好的性能。