如何在不启动任务的情况下构造任务
本文关键字:任务 情况下 启动 | 更新日期: 2023-09-27 18:09:47
我想使用这个Task<TResult>
构造函数。我似乎搞不懂语法。有人能纠正我的代码吗?
此外,我认为如果Task
以这种方式构建,它就不会启动,这是对的吗?
我认为我需要的构造函数是:
Task<TResult>(Func<Object, TResult>, Object)
我得到的错误是:
参数1:无法从"方法组"转换为"
System.Func<object,int>
">
static void Main(string[] args)
{
var t = new Task<int>(GetIntAsync, "3"); // error is on this line
// ...
}
static async Task<int> GetIntAsync(string callerThreadId)
{
// ...
return someInt;
}
var t = new Task<int>(() => GetIntAsync("3").Result);
或
var t = new Task<int>((ob) => GetIntAsync((string) ob).Result, "3");
为了避免使用lambda,您需要编写这样的静态方法:
private static int GetInt(object state)
{
return GetIntAsync(((string) state)).Result;
}
然后:
var t = new Task<int>(GetInt, "3");
要使用接受object state
参数的Task
构造函数,必须有一个也接受object
参数的函数。一般来说,这并不方便。存在此构造函数的原因是为了避免在热路径中分配对象(闭包(。对于正常使用来说,闭包的开销可以忽略不计,避免它们会使代码莫名其妙地复杂化。因此,这是您应该使用的构造函数:
public Task (Func<TResult> function);
以lambda作为参数:
() => GetIntAsync("3")
不过,您的情况有一个特殊之处:传递给构造函数的lambda返回Task<int>
。这意味着泛型类型TResult
被解析为Task<int>
,因此您最终得到一个嵌套任务:
var t = new Task<Task<int>>(() => GetIntAsync("3"));
启动外部任务将导致创建内部任务。要获得最终结果,您必须使用await
运算符两次,一次用于完成外部任务,另一次用于内部任务:
static async Task Main(string[] args)
{
var outerTask = new Task<Task<int>>(() => GetIntAsync("3"));
//...
outerTask.Start(TaskScheduler.Default); // Run on the ThreadPool
//outerTask.RunSynchronously(TaskScheduler.Default) // Run on the current thread
//...
Task<int> innerTask = await outerTask; // At this point the inner task has been created
int result = await innerTask; // At this point the inner task has been completed
}
我发现异步lambda效果最好,因为:
- 这项工作是通过调用方法开始的,这意味着您不需要调用
.Start
,所以不可能忘记执行它 - 这种方法不会受到内部/外部任务问题的影响;你总是在等待正确的任务,而不是它的包装
Func<Task<int>> f = async () => await WorkAsync(2);
Console.WriteLine("Sleeping before start");
await Task.Delay(100);
Console.WriteLine("Waking up to start");
var result = await f();
Console.WriteLine($"The work is done and the answer is: {result}");
这将产生以下输出:
Sleeping before start
Waking up to start
Starting 2
Ending 2
The work is done and the answer is: 4
也有类似的问题,我们希望在不重新启动任务的情况下确保任务可重复使用。Rx救了我们的培根。你需要新的套装System.Reactive
。
请参阅下面的GetIntTask
返回一个未启动的任务,GetIntOnce
确保任务启动,并且任务的结果被缓冲并回放给任何调用方(使用Replay(1)
(
async Task Main()
{
var awaitableResult = GetIntOnce();
Console.WriteLine("Has the task started yet? No!");
var firstResult = await awaitableResult;
Console.WriteLine($"Awaited Once: {firstResult}");
var secondResult = await awaitableResult;
Console.WriteLine($"Awaited Twice: {secondResult}");
}
public static IObservable<int> GetIntOnce()
{
return Observable.FromAsync(() =>
{
var t = GetIntTask();
t.Start();
return t;
}).Replay(1).AutoConnect();
}
public static Task<int> GetIntTask()
{
var t = new Task<int>(
() =>
{
Console.WriteLine("Task is running!");
return 10;
});
return t;
}
输出为:
Has the task started yet? No!
Task is running!
Awaited Once: 10
Awaited Twice: 10
.net 5中的一种新方法(实际上我不知道你什么时候可以开始这样做,我现在正在使用.net 5(
Task<int> MyStartLaterTask = null;
//... Some Time Later
MyStartLaterTask ??= GetIntAsync(string callerThreadId);
await MyStartLaterTask;
我很感激这并不完全是";先定义后使用";但据我所见,这也起到了同样的作用。我有一个循环,我想定义一个只在需要时运行并且只运行一次的任务。这就做到了,它创建了一个任务,我可以在第一次需要时分配它,然后在需要输出时等待它。
//creating task
var yourTask = Task<int>.Factory.StartNew(() => GetIntAsync("3").Result);
//...
int result = yourTask.Result;
更新:
是的,不幸的是,它确实启动了任务。使用上面提到的代码:
//creating task
var yourTask = new Task<int>(() => GetIntAsync("3").Result);
//...
// call task when you want
int result = yourTask.Start();