【问题标题】:How to return the result from Task?如何从任务返回结果?
【发布时间】:2015-12-09 19:00:34
【问题描述】:

我有以下方法:

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

【问题讨论】:

    标签: c# multithreading


    【解决方案1】:

    您可能想在此处使用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&lt;int&gt; 让调用者决定是等待、继续还是触发并忘记它.

    请参阅以下示例。我已将所有内容重新安排到一个类中:

    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);
        }
    }
    

    【讨论】:

    • 我需要返回之前缓存的数据,你却返回了Task
    • @ivan_petrushenko 你需要学习任务异步模式。在网上查找有关此概念的信息。顺便说一句,检查我更新的答案。
    • @ivan_petrushenko 我相信我的回答是您提升.NET 知识和技能的良好开端。为什么不花点时间了解s a Task, what's async-await, an out` 参数呢?这只是我的建议。
    • 实际上,经过进一步检查,还有更多问题:在Task&lt;int&gt; 实例上调用Task.Run,滥用Interlocked.Exchange(在这种情况下它不会保护任何东西,你正在设置引用,它是所有系统上的原子操作,遵循await 插入的内存屏障) - 也可能是其他东西。
    • 如果他们一次收到 20 个数据请求会发生什么......它会发送 20 个任务来获取它吗?
    【解决方案2】:

    您需要将缓存的值存储在您的类中,然后在查询该值时检查您是否已经在更新它。如果您只是返回缓存的值。如果没有,则启动新查询并返回缓存值。

    【讨论】:

      【解决方案3】:

      如果您根本不需要等待任务完成,您可以让函数启动任务并处理 ContinueWith 中的设置

          public int GetPreviousData()
          {
              Task.Run((Func<int>)getData).ContinueWith(t => _cachedData = t.Result);            
              return _cachedData; // some previous value
          }
      

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

      【讨论】:

      • 当我们有async-await时为什么要使用显式延续
      • @ivan_petrushenko 你被困在 .net 3.5 中:D 这个答案与 .NET 4.5.2 有什么关系? TAP/TPL 自 .NET 4.0(2009 年?)起可用
      • @ivan_petrushenko 也许你有自己的 C# 编译器,因为Task&lt;int&gt; 总是有一个Result 属性:(
      • @MatíasFidemraizer :关于显式延续:因为在这种情况下不需要等待,甚至不需要。只需触发并忘记,因此该函数会立即返回先前的值(无论出于何种设计原因)当然,可以在 GetPreviousData 中的额外任务中调用可等待的设置器,但不会对可读性有太大帮助(尽管如果等待可能会更好是 IO 绑定的)
      • 这似乎是更好的答案,因为 OP 似乎并不关心比赛,并且满足了所有要求。您甚至可以将您的任务缩短为Task.Run(() =&gt; _cachedData = getData())(由于您没有指定调度程序,因此延续不会给您任何东西),并保持几乎相同的行为并提高可读性。显然存在错误处理的问题(在 .NET 4 中,如果 getData 失败,你会得到可怕的 UnobservedTaskException;在以后的版本中只是静默失败),但这似乎超出了问题的范围。
      猜你喜欢
      • 2013-03-22
      • 2017-07-04
      • 1970-01-01
      • 1970-01-01
      • 2021-07-31
      • 1970-01-01
      • 2016-11-11
      • 1970-01-01
      • 2017-06-02
      相关资源
      最近更新 更多