【问题标题】:Check if Object is Task or Task<TResult>检查 Object 是 Task 还是 Task<TResult>
【发布时间】:2017-10-17 00:33:29
【问题描述】:

在下面的示例代码中(由于 if 检查无效,它无法编译),我需要确定 returnValue 是“TResult 的任务”还是任务或其他类型。如果是 TResult 的 Task 我可以访问并记录 Result 属性,如果是 Task,没有返回值,我会记录“[Task]”,如果不是这两个,那么我可以直接记录返回值或记录一个[“空”]

public void LogReturnValue(obj returnValue)
{
   var valueToLog = "";
   if(returnValue is Task)
   {
       valueToLog = "[Task]";
   } else if(returnValue is Task<T>)
   {
     valueToLog = returnValue.Result;
   } 
   else 
   {
     valueToLog = returnValue ?? "[null]"
   }
   this.logger.Log(valueToLog);
}

我想确定returnValue是TResult的Task还是Task,如果是后者,我需要从Result中提取适当的值。一 我的想法是检查 IsGenericType 属性,但我不确定 如果这将永远有效。

【问题讨论】:

  • Task&lt;T&gt; 扩展 Task。如果对象是Task&lt;T&gt;,那么它将通过第一次检查。泛型检查必须非泛型类型的测试之前进行。但是这个问题对我来说意味着您正在做一些奇怪的事情并且可能是错误的;你能说说你为什么要做这种奇怪的事情吗?
  • 此外,获取任务的结果几乎总是非常错误的事情。该任务可能无法完成!这样做可以使程序死锁!如果任务完成时出现异常怎么办?这将使您的程序崩溃。您的代码不起作用,但如果它确实起作用,它会在很多时候挂起或崩溃您的程序。 你为什么要这样做?
  • @EricLippert 如果我的对象是一个任务,则此问题上下文之外的代码可确保在调用此方法之前它是一个已完成的任务,所以这不是问题。如果它以异常完成,我可以在这里检查并记录一些关于它的信息。我想要完成的是记录 returnValue,如果没有,那很好。该代码只是一个“示例代码”,我明确表示它不会编译,它只是为了帮助传达我想要完成的任务。
  • 我知道将 async/await 与同步等待和由此产生的死锁混合是很危险的,但这个问题只是为了看看我如何在运行时提取任务的结果,而对象可能是几乎任何东西。我不明白为什么人们不赞成它。我认为只要您知道自己在做什么,访问 Result 是合理的。同样,我知道这不是最佳做法,应尽可能避免。
  • +1 票。对我来说,我一遍又一遍地使用的典型场景是异步运行器。我以 fire&forget 的方式执行一些异步任务,但是 runner 附加了ContinueWith,然后我检查结果(即故障、取消等)并以很好的方式向用户显示结果。一切都在掌控之中,但我需要Result

标签: c# .net async-await task


【解决方案1】:

尽管 Eric Lippert 提到您不应该获取 Task.Result,但仍有几个问题需要回答:

  1. 如何检查一个对象是否是泛型类型

    obj.GetType().IsGenericType
    
  2. 是否是任务

    obj.GetType().GetGenericTypeDefinition() == typeof(Task<>)
    
  3. 如何访问通用属性值

    obj.GetType().GetProperty("Result").GetValue(obj) // This value could be null
    

【讨论】:

  • 3.也可以是((dynamic)obj).Result
  • 是的,它可以工作并且代码很清晰,但是生成的代码让我感到害怕。
  • 我错过了#2
  • 在有人被咬之前——继续表面上你会得到任务,但第二次检查会失败,你会得到结果。文本表示为"System.Threading.Tasks.VoidTaskResult"
  • 注意:你不能依赖 #2 来确定它是 Task,取决于运行时,它可能是 System.Runtime.CompilerServices.AsyncTaskMethodBuilder1+AsyncStateMachineBox@987654327 @
【解决方案2】:

可能有一种更清洁的方法,但这是可行的,您可以在此基础上进行构建。 "as" 将检查类型或基类型是一项任务,然后您可以检查类型以查看它是否具有泛型类型参数。因为它是动态的,所以检查 Result 是否是一个属性是在运行时而不是编译时完成的。

static void outType(object returnValue)
{
    dynamic task = returnValue as Task;
    if ( task != null )
    {
        var gargs = returnValue.GetType().GenericTypeArguments;

        if (gargs.Count() == 0)
        {
            Console.WriteLine("Task");
        }
        else
        {
            var result = task.IsCompleted ? task.Result : "[Not Complete]";
            Console.WriteLine("Task<{0}> : {1}", gargs[0].Name, result);
        }
    }
}

【讨论】:

  • 这与 cmets 中针对已接受答案的问题相同。 IE。如果任务实际上是Task&lt;System.Threading.Tasks.VoidTaskResult&gt;,那么代码将尝试检索Result 属性,但这会失败,因为就任务而言Task&lt;System.Threading.Tasks.VoidTaskResult&gt; 实际上与非泛型Task 类型相同-运行时的相关部分。
【解决方案3】:

我觉得前面的答案都没有明确回答所提出的问题,或者我宁愿错过了问题意图的标记。

我从这个问题的解释是:

“我如何判断 'returnValue' 是属于 'Task'、'Task' 类型还是其他类型?

此测试代码可用于确定:

    Task voidTask = new Task(() => { return; });
    Task<object> valTask = new Task<object>(() => { return null; });

    Type voidTaskType = voidTask.GetType();
    Type valTaskType = valTask.GetType();

    Console.WriteLine($"voidTaskType is Task : {voidTask is Task}");
    Console.WriteLine($"valTaskType is Task : {valTask is Task}");
    Console.WriteLine($"voidTaskType.IsGenericType : {voidTaskType.IsGenericType}");
    Console.WriteLine($"valTaskType.IsGenericType : {valTaskType.IsGenericType}");

结果如下:

voidTaskType is Task : True
valTaskType is Task : True
voidTaskType.IsGenericType : False
valTaskType.IsGenericType : True

【讨论】:

    【解决方案4】:

    让我们保持简单,好吗?这个方法会给你思路:

    object GetTaskResult(Task task, out bool hasResult) {
        var resultProperty = task.GetType().GetProperty("Result");
        hasResult = resultProperty != null;
        return hasResult ? resultProperty.GetValue(task) : null;
    }
    

    它告诉你任务是否有结果,如果有则返回结果。

    替代版本

    bool GetTaskResult(Task task, out object result) {
        var resultProperty = task.GetType().GetProperty("Result");
        if (resultProperty == null) {
            result = null;
            return false;
        }
        result = resultProperty.GetValue(task);
        return true;
    }
    

    我已经针对VoidTaskResult 对其进行了测试,它可以工作。

    【讨论】:

      【解决方案5】:

      我注意到,在 .Net 5 中,从不需要空属性访问器 ?.,因为所有任务都存在 Result 属性。它被初始化为System.Threading.Tasks.VoidTaskResult。这不是获取价值的最有效方式,但它很简洁。

      var result = task.GetType().GetProperty(nameof(Task<object>.Result))?.GetValue(task);
      

      【讨论】:

        【解决方案6】:

        这可能会有所帮助。

        用途:

        类型类型 = abc.GetType().GetProperty("Item").PropertyType

        参考: How to get the type of T from a member of a generic class or method?

        【讨论】:

        • 那行不通,如果 returnValue 不是 Task 它可能是其他任何东西,不一定是 Task
        • 这两种方法你都试过了吗?我很确定第一个本身就足够了。
        • Var 也不能这样工作。除非将 obj 替换为 string,否则代码 valueToLog = returnValue; 将无法编译
        • @Shree 你的意思是如果它不是一个 Task 或一个 IEnumerable 那么它一定是 Task ?这不是真的,没关系,我认为你误解了这个问题。
        • 如果 IEnumerable,枚举它并检查第一项的类型。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-07
        • 1970-01-01
        • 1970-01-01
        • 2012-04-07
        • 1970-01-01
        相关资源
        最近更新 更多