如何从Task返回结果?

5
我有以下几种方法:
public int getData() { return 2; } // suppose it is slow and takes 20 sec

// pseudocode
public int GetPreviousData()
{
    Task<int> t = new Task<int>(() => getData());
    return _cachedData; // some previous value
    _cachedData = t.Result; // _cachedData == 2
}

我不想等待已经运行的操作结果。

我想返回_cachedData并在Task完成后更新它。

如何实现?我正在使用.net framework 4.5.2

3个回答

7
您可能想在此处使用一个“out”参数:
public Task<int> GetPreviousDataAsync(out int cachedData)
{
    Task<int> t = Task.Run(() => getData());
    cachedData = _cachedData; // some previous value
    return t; // _cachedData == 2
}

int cachedData;
cachedData = await GetPreviousDataAsync(out int cachedData);

请注意Task.Run这个东西:它使用线程池启动一个任务,并返回一个Task<int>,让调用者决定是否需要等待、继续或者fire and forget
请看下面的示例。我已经将所有内容重新排列到一个类中:
class A
{
    private int _cachedData;
    private readonly static AutoResetEvent _getDataResetEvent = new AutoResetEvent(true);

    private int GetData()
    {
        return 1;
    }

    public Task<int> GetPreviousDataAsync(out int cachedData)
    {
        // This will force calls to this method to be executed one by one, avoiding
        // N calls to his method update _cachedData class field in an unpredictable way
        // It will try to get a lock in 6 seconds. If it goes beyong 6 seconds it means that 
        // the task is taking too much time. This will prevent a deadlock
        if (!_getDataResetEvent.WaitOne(TimeSpan.FromSeconds(6)))
        {
            throw new InvalidOperationException("Some previous operation is taking too much time");
        }

        // It has acquired an exclusive lock since WaitOne returned true

        Task<int> getDataTask = Task.Run(() => GetData());
        cachedData = _cachedData; // some previous value

        // Once the getDataTask has finished, this will set the 
        // _cachedData class field. Since it's an asynchronous 
        // continuation, the return statement will be hit before the
        // task ends, letting the caller await for the asynchronous
        // operation, while the method was able to output 
        // previous _cachedData using the "out" parameter.
        getDataTask.ContinueWith
        (
            t =>
            {
                if (t.IsCompleted)
                    _cachedData = t.Result;

                // Open the door again to let other calls proceed
                _getDataResetEvent.Set();
            }
        );

        return getDataTask;
    }

    public void DoStuff()
    {
        int previousCachedData;
        // Don't await it, when the underlying task ends, sets
        // _cachedData already. This is like saying "fire and forget it"
        GetPreviousDataAsync(out previousCachedData);
    }
}

@ivan_petrushenko 你需要学习任务异步模式。在网上搜索有关这个概念的信息。顺便说一句,检查一下我的更新答案。 - Matías Fidemraizer
此外,OP还说“我正在使用.NET 4.5.2”(就像其他答案中的评论所说的“这种方式不起作用”的辩解)。我的意思是,很明显OP需要学习这些概念。 - Matías Fidemraizer
@KirillShlenskiy 完成了... 看看这是否对您合适 - Matías Fidemraizer
1
如果同时收到20个数据请求,会发生什么情况?它会发送20个任务来获取数据吗? - Tim B
@TimB 这个需求是否超出了这个问答的范围?我的意思是,我们可以解决很多问题。如果其他类成员使用_cachedData而整个类不是线程安全的,会发生什么......我认为OP应该一步一步地解决问题。我错了吗? - Matías Fidemraizer
显示剩余17条评论

1
如果您不需要等待任务完成,可以让函数开始任务并在 ContinueWith 中处理设置。
    public int GetPreviousData()
    {
        Task.Run((Func<int>)getData).ContinueWith(t => _cachedData = t.Result);            
        return _cachedData; // some previous value
    }

如果竞态条件是一个问题,你可以先将 _cachedData 赋值给一个变量,然后运行任务并立即返回该变量,但如果 getData 需要任何时间,那就不应该是一个问题。

1
为什么在有async-await的情况下还要使用显式continuations? - Matías Fidemraizer
@ivan_petrushenko 你的思维还停留在 .NET 3.5 上 :D .NET 4.5.2 和这个回答有什么关系呢?TAP/TPL 自 .NET 4.0 (2009?) 就已经可用了。 - Matías Fidemraizer
@ivan_petrushenko 或许你有自己的C#编译器,因为 Task<int> 总是会有一个 Result 属性 :( - Matías Fidemraizer
@MatíasFidemraizer:关于显式续延:因为在这种情况下不需要等待,甚至不希望等待。只需点火并忘记,因此函数立即返回先前的值(出于任何设计原因)。当然,在GetPreviousData中可以调用可等待的setter,但对于可读性来说并没有太大作用(尽管如果等待是IO绑定的话可能会更好)。 - Me.Name
1
这似乎是更好的答案,因为OP似乎不关心种族,而且所有要求都得到满足。你甚至可以将任务缩短为Task.Run(() => _cachedData = getData())(如果你没有指定调度程序,则继续不会给你任何东西),并保持几乎相同的行为和改进的可读性。显然,存在错误处理问题(在.NET 4中,如果getData失败,你将获得可怕的UnobservedTaskException;在后来的版本中只有静默失败),但这似乎超出了问题的范围。 - Kirill Shlenskiy

1
你需要在类中存储缓存值,当查询该值时,检查是否已经在更新。如果正在更新,则返回缓存的值。否则启动新的查询并返回缓存的值。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接