我有一个C#应用程序需要在不破坏数据流的情况下将数据输入流热交换到新的处理程序类。
要做到这一点,我必须在单个线程中执行多个步骤,而不会由于CPU切换而在它们之间运行任何其他线程(大多数数据接收线程)。
这是情况的简化版本,但它应该说明问题。
void SwapInputHandler(Foo oldHandler, Foo newHandler)
{
UnhookProtocol(oldHandler);
HookProtocol(newHandler);
}
这两行(unhook和hook)必须在同一个cpu片中执行,以防止任何数据包通过,以防其他线程在它们之间执行。
如何使用C#线程方法确保这两个命令按顺序运行?
修改
似乎有些混乱所以我会尝试更具体。我并不意味着同时执行,只是在相同的cpu时间片中,因此在这两个完成之前没有线程执行。锁定不是我正在寻找的,因为这只会阻止在两个命令运行之前再次执行此代码。在完成这些命令之前,我需要阻止任何线程运行。另外,我再次说这是我的问题的简化版本,所以不要试图解决我的例子,请回答这个问题。
答案 0 :(得分:4)
在单个时间片中执行操作根本没有帮助 - 操作可以在另一个核心或处理器上并行执行,并在执行交换时访问流。您将不得不使用锁定来阻止每个人在流处于不一致状态时访问流。
答案 1 :(得分:1)
您的数据接收线程需要锁定访问处理程序指针,您需要锁定更改处理程序指针。
或者,如果您的处理程序是单个变量,您可以使用Interlocked.Exchange()以原子方式交换值。
答案 2 :(得分:0)
为什么不从另一个方向去做,并让有问题的线程处理交换。据推测,当有待处理的数据时,某些东西会被唤醒,并将其传递给当前的Foo。您是否可以向该线程发布通知,以便下次唤醒时需要在新处理程序中交换?我想,那会更不用说了。
答案 3 :(得分:0)
好的 - 回答你的具体问题。
您可以枚举进程中的所有线程并在每个线程上调用Thread.Suspend()(活动的除外),进行更改,然后调用Thread.Resume()。
答案 4 :(得分:0)
假设您的处理程序是线程安全的,我的建议是在处理程序上编写一个公共包装器,使用私有锁执行所需的所有锁定,以便您可以安全地更改后台处理程序。
如果这样做,您还可以使用ReaderWriterLockSlim来访问允许并发读取访问的包装处理程序。
或者你可以设计你的包装类和处理程序clases,使得不需要锁定,并且可以使用简单的互锁写或比较交换来完成处理程序的淹没。
以下是示例:
public interface IHandler
{
void Foo();
void Bar();
}
public class ThreadSafeHandler : IHandler
{
ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
IHandler wrappedHandler;
public ThreadSafeHandler(IHandler handler)
{
wrappedHandler = handler;
}
public void Foo()
{
try
{
rwLock.EnterReadLock();
wrappedHandler.Foo();
}
finally
{
rwLock.ExitReadLock();
}
}
public void Bar()
{
try
{
rwLock.EnterReadLock();
wrappedHandler.Foo();
}
finally
{
rwLock.ExitReadLock();
}
}
public void SwapHandler(IHandler newHandler)
{
try
{
rwLock.EnterWriteLock();
UnhookProtocol(wrappedHandler);
HookProtocol(newHandler);
}
finally
{
rwLock.ExitWriteLock();
}
}
}
请注意,如果处理程序的方法需要原子操作,这仍然不是线程安全的,那么你需要在steps之间使用更高阶的锁定,或者在你的包装类上添加方法来支持线程安全的原子操作(类似于EndTreadSafeBlock()包含EndTreadSafeBlock(),用于锁定包装的处理程序,以便为一系列操作进行写入。
答案 5 :(得分:0)
你不能,而且你不能这样做是合乎逻辑的。你能做的最好的事情就是避免任何其他线程破坏这两个行为之间的状态(正如已经说过的那样)。
这就是为什么你不能:
想象一下,当你在那个块上时,有一个块告诉操作系统永远不会进行线程切换。这在技术上是可行的,但会导致各地的饥饿。
你可能认为你的线程是唯一被使用的线程,但这是一个不明智的假设。有垃圾收集器,有与线程池线程一起使用的异步操作,外部引用,例如COM对象可以跨越它自己的线程(在你的内存空间中),这样当你在它时没有人可以进展。
想象一下,你在HookOperation方法中做了很长时间的操作。它涉及许多非泄漏操作,但是,由于垃圾收集器无法接管以释放资源,因此最终不会留下任何内存。或者想象你调用一个使用多线程来处理你的请求的COM对象......但它无法启动新线程(它可以启动它们但它们永远不会运行)然后加入它们等待它们完成然后再来回来......因此你加入了自己,永远不会回来!!。
答案 6 :(得分:0)
正如其他海报已经说过的那样,您无法从用户模式代码强制执行系统范围的关键部分。但是,您不需要它来实现热交换。
以下是如何。
使用与热插拔Foo
对象相同的接口实现代理。代理人应致电HookProtocol
并永不解开(直到您的应用停止)。它应包含对当前Foo
处理程序的引用,您可以在需要时将其替换为新实例。代理应将从钩子函数接收的数据定向到当前处理程序。此外,它还应提供一种原子替换当前Foo
处理程序实例的方法(有许多方法可以实现它,从简单的互斥锁到无锁)。