如果任务等待's,则任务状态更改为RanToCompletion
本文关键字:任务 状态 RanToCompletion 等待 如果 | 更新日期: 2023-09-27 18:05:41
该问题描述了在MSDN开发者论坛中发现的相同问题。这个问题没有一个公认的答案,给出的任何答案都不能适用于我的情况(因此是另一个问题)。
这个问题也是我之前问过的一个问题的衍生,但是由于性质不同,问题也更具体,所以我要问一个新的问题。
完整的代码可以在这里找到:http://pastebin.com/uhBGWC5e
*唯一改变的是任务完成检查(while
-> Task.WhenAll
)。
等待任务内部异步操作时,任务状态变为RanToCompletion
,尽管任务仍在运行。
现在,让我们看看设置:
// Start async.
Task t1 = Task.Factory.StartNew(Accept, s1);
Task t2 = Task.Factory.StartNew(Accept, s1);
Task.WhenAll(t1, t2).Wait();
Accept
法:
public static async void Accept(object state)
{
TcpListenerEx server = (TcpListenerEx) state;
IPEndPoint endPoint = server.LocalEndpoint as IPEndPoint;
Log("Accepting clients on {0}", endPoint);
while (true)
{
var client = server.AcceptTcpClientAsync();
if (client == null)
{
Log("Null error on accept");
break;
}
TcpClient connected = (TcpClient) client;
servers[server].Add(connected);
bool stop = await Task<Task<bool>>.Factory.StartNew(Listen, connected).Unwrap();
if (stop == true)
{
break;
}
}
// Stop the server.
server.Stop();
Log("Stoppped {0}", endPoint);
}
由于TaskStatus改变为RanToCompletion, Task.WhenAll().Wait()
调用标记自己完成得相当快,导致程序被进一步执行,最终被终止。
但是,从理论上讲,Accept
任务不应该停止,它一直在监听连接,直到显式停止。
这里的问题是什么,导致任务被标记为RanToCompletion
过早?
我可以用更少的代码重现这个问题:
void Main()
{
Task t1 = Task.Factory.StartNew(Accept);
t1.Wait();
Console.WriteLine("Main ended");
}
public static async void Accept()
{
while (true)
{
await Task.Delay(1000);
}
Console.WriteLine("Stoppped");
}
但这是正确的:
void Main()
{
Task t1 = Accept();
t1.Wait();
Console.WriteLine("Main ended");
}
public static async Task Accept()
{
while (true)
{
await Task.Delay(1000);
}
Console.WriteLine("Stoppped");
}
基本上,通过使用Task.Factory.StartNew()
,您正在创建一个基于派生的单独线程的Task
来调用给定的委托(Accept()
方法)。Accept
方法本身(像任何好的async
方法一样)实际上立即返回。因此,调用它的线程立即完成了它的任务,因此创建用来表示该线程的Task
也立即完成。
如果你允许Accept()
返回一个Task
而不是void
,那么它返回的Task
就是你应该等待的,如果你想等到它运行完所有的await
。
有两个错误:async void
和Task.Factory.StartNew
。这两个都是不好的做法。
首先,async void
不允许调用代码知道它何时完成。所以你在等待并不重要;async void
方法会很快返回,你的应用程序不知道Accept
什么时候真正结束。要解决这个问题,将async void
替换为更合适的async Task
。
第二,StartNew
不理解异步委托。StartNew
是一个非常低级的API,不应该在99.99%的产品代码中使用。用Task.Run
代替。
public static async Task Accept(object state);
Task t1 = Task.Run(() => Accept(s1));
Task t2 = Task.Run(() => Accept(s1));
Task.WaitAll(t1, t2);
我也遇到过类似的问题。
我有一个阅读器任务,我想触发读取数据并处理它。因此我使用:
var readTrigger = new ManualResetEventSlim();
为了在需要时优雅地停止线程,我使用了CancellationToken。因为我不希望我的任务持续运行和轮询数据(而是使用事件来通知新数据),所以我使用了一个阻塞函数来在无事可做时将任务置于睡眠状态。因此,我必须侦听两个事件,这是使用WaitHandle完成的。
var action = WaitHandle.WaitAny(new WaitHandle[] { readTrigger.WaitHandle, cancellationToken.WaitHandle });
可能有一个不同的,更好的解决方案与WaitHandle与async/await(如果有人知道请告诉我:))
该函数仍然包含异步函数,尽管我必须等待。这导致了一个异步线程函数,它具有以下函数头:
private async Task ReadDataTask(CancellationToken cancellationToken)
WaitHandle函数阻塞等待,直到至少一个事件被触发。由于我正在使用WaitHandle,我需要使用一个单独的线程来防止阻塞我的整个应用程序。我使用下面的调用在一个单独的线程中启动我的Task:
var t = new Task(async() => await ReadDataTask(ct), ct, TaskCreationOptions.LongRunning);
我遇到了同样的问题,任务t在ReadDataTask函数返回之前终止。因为我必须使用WaitHandle(或者让我们说想要),所以前面的解决方案都不适合我。
我的问题的解决方法相当简单。我没有使用await来等待我的ReadDataTask,而是使用了来自返回任务的wait,这会导致以下调用:
var t = new Task(() => ReadDataTask(ct).Wait(), ct, TaskCreationOptions.LongRunning);
我希望我能帮你解决这个问题。
亲切的问候