如何在不启动任务的情况下构造任务

本文关键字:任务 情况下 启动 | 更新日期: 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效果最好,因为:

  1. 这项工作是通过调用方法开始的,这意味着您不需要调用.Start,所以不可能忘记执行它
  2. 这种方法不会受到内部/外部任务问题的影响;你总是在等待正确的任务,而不是它的包装
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();
相关文章: