我有这两个类:LineWriter 和 AsyncLineWriter。我的目标是允许调用者对 AsyncLineWriter 的方法进行多个挂起的异步调用排队,但在幕后永远不会真正允许它们并行运行;也就是说,他们必须以某种方式排队等待彼此。就是这样。这个问题的其余部分给出了一个完整的示例,因此我真正要问的内容绝对没有歧义。
LineWriter 有一个同步方法 (WriteLine),运行大约需要 5 秒:
public class LineWriter
{
public void WriteLine(string line)
{
Console.WriteLine("1:" + line);
Thread.Sleep(1000);
Console.WriteLine("2:" + line);
Thread.Sleep(1000);
Console.WriteLine("3:" + line);
Thread.Sleep(1000);
Console.WriteLine("4:" + line);
Thread.Sleep(1000);
Console.WriteLine("5:" + line);
Thread.Sleep(1000);
}
}
AsyncLineWriter 只是封装了LineWriter 并提供了一个异步接口(interface)(BeginWriteLine 和EndWriteLine):
public class AsyncLineWriter
{
public AsyncLineWriter()
{
// Async Stuff
m_LineWriter = new LineWriter();
DoWriteLine = new WriteLineDelegate(m_LineWriter.WriteLine);
// Locking Stuff
m_Lock = new object();
}
#region Async Stuff
private LineWriter m_LineWriter;
private delegate void WriteLineDelegate(string line);
private WriteLineDelegate DoWriteLine;
public IAsyncResult BeginWriteLine(string line, AsyncCallback callback, object state)
{
EnterLock();
return DoWriteLine.BeginInvoke(line, callback, state);
}
public void EndWriteLine(IAsyncResult result)
{
DoWriteLine.EndInvoke(result);
ExitLock();
}
#endregion
#region Locking Stuff
private object m_Lock;
private void EnterLock()
{
Monitor.Enter(m_Lock);
Console.WriteLine("----EnterLock----");
}
private void ExitLock()
{
Console.WriteLine("----ExitLock----");
Monitor.Exit(m_Lock);
}
#endregion
}
正如我在第一段中所说,我的目标是一次只允许一个挂起的异步操作。如果我不需要在这里使用锁,我会真的喜欢它;即,如果 BeginWriteLine 可以返回一个始终立即返回的 IAsyncResult 句柄,并且仍保留预期的行为,那就太好了,但我不知道该怎么做。因此,下一个解释我所追求的最好方法似乎就是使用锁。
仍然,我的“锁定内容”部分没有按预期工作。我希望上面的代码(在异步操作开始之前运行 EnterLock 并在异步操作结束之后运行 ExitLock)在任何给定时间只允许一个挂起的异步操作运行。
如果我运行以下代码:
static void Main(string[] args)
{
AsyncLineWriter writer = new AsyncLineWriter();
var aresult = writer.BeginWriteLine("atest", null, null);
var bresult = writer.BeginWriteLine("btest", null, null);
var cresult = writer.BeginWriteLine("ctest", null, null);
writer.EndWriteLine(aresult);
writer.EndWriteLine(bresult);
writer.EndWriteLine(cresult);
Console.WriteLine("----Done----");
Console.ReadLine();
}
我希望看到以下输出:
----EnterLock----
1:atest
2:atest
3:atest
4:atest
5:atest
----ExitLock----
----EnterLock----
1:btest
2:btest
3:btest
4:btest
5:btest
----ExitLock----
----EnterLock----
1:ctest
2:ctest
3:ctest
4:ctest
5:ctest
----ExitLock----
----Done----
但我看到了以下内容,这意味着它们都只是并行运行,就好像锁没有作用一样:
----EnterLock----
----EnterLock----
----EnterLock----
1:atest
1:btest
1:ctest
2:atest
2:btest
2:ctest
3:atest
3:btest
3:ctest
4:atest
4:btest
4:ctest
5:atest
5:btest
5:ctest
----ExitLock----
----ExitLock----
----ExitLock----
----Done----
我假设这些锁被“忽略”了,因为(如果我错了请纠正我)我是从同一个线程锁定它们的。我的问题是:我如何获得预期的行为?而“不要使用异步操作”也不是一个可以接受的答案。
有一个更大、更复杂的现实生活用例,使用异步操作,有时不可能有多个真正并行的操作,但我至少需要模拟行为,即使这意味着对操作进行排队并一个接一个地运行它们。具体来说,AsyncLineWriter 的接口(interface)不应该改变,但它的内部行为需要以某种方式排队以线程安全的方式提供的任何异步操作。另一个问题是我无法将锁添加到 LineWriter 的 WriteLine 中,因为在我的真实情况下,这是一个我无法更改的方法(尽管在本例中,这样做确实会给我预期的输出)。
一些旨在解决类似问题的代码的链接可能足以让我走上正确的道路。或者也许是一些替代的想法。谢谢。
附言如果您想知道什么样的用例可能会使用这样的东西:它是一个维护网络连接的类,一次只能激活一个操作;我对每个操作都使用异步调用。没有明显的方法可以让真正并行的通信线路在单个网络连接上继续进行,因此它们将需要以一种或另一种方式相互等待。
最佳答案
锁在这里是不可能工作的。您正在尝试在一个线程(调用线程)上进入锁并在另一个线程(ThreadPool)上退出。
由于 .Net 锁是可重入的,因此在调用者线程上第二次输入它不会等待。 (如果它等待,它会死锁,因为你只能退出那个线程上的锁)
相反,您应该创建一个在锁中调用同步版本的方法,然后异步调用该方法。
这样,您将在异步线程而不是调用者线程上进入锁,并在方法完成后在同一线程上退出。
但是,这是低效的,因为您正在浪费等待锁的线程。
最好让一个线程有一个委托(delegate)队列在其上执行,这样调用异步方法将启动该线程的一个副本,或者如果线程已经在运行,则将其添加到队列中。
当此线程完成队列时,它将退出,然后在您下次调用异步方法时重新启动。
我在类似的情况下做过这件事;见 my earlier question 。
请注意,您需要自己实现 IAsyncResult
,或者使用回调模式(通常要简单得多)。
关于c# - 如何强制 C# 异步操作以...同步方式运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7920043/