【问题标题】:return Task.ContinueWith<TResult>() without knowing TResult在不知道 TResult 的情况下返回 Task.ContinueWith<TResult>()
【发布时间】:2013-07-11 14:20:25
【问题描述】:

如果我只能访问任务,如何使用反射或任何其他方式创建和返回延续任务?

我需要一种方法让延续中的异常返回给原始调用者。据我所知,这只能通过返回延续任务而不是原始任务来完成。问题在于我不知道任务的结果类型,因此无法创建正确的延续任务。

编辑: 我无法更改签名类型。我有许多返回 Task 对象的接口,不能指望客户端获得 Task 结果。这些接口是 WCF 契约。我想在“核心”逻辑方法完成后做一些额外的验证逻辑,并在需要时抛出异常。此异常必须返回给客户端,但它当前不会返回,因为我还没有返回继续任务。另外我事先不知道类型,因为我正在应用 postsharp 方面并使用 OnExit() 覆盖,这使我可以访问我知道是 Task 但它可以是任意数量的 Task 对象的返回值TResult 仅在运行时知道。

using System;
using System.Threading.Tasks;

    namespace TaskContinueWith
    {
        internal class Program
        {
            private static void Main(string[] args)
            {
                try
                {
                    Task<string> myTask = Interceptor();
                    myTask.Wait();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }

                Console.ReadLine();
            }

            private static Task<string> Interceptor()
            {
                Task<string> task = CoreLogic(); //Ignore


                Task unknownReturnType = task; //This is what I have access to. A Task object which can be one of numerous Task<TResult> types only known at runtime.

                Task continuation = unknownReturnType.ContinueWith(
                    t =>
                        {
                         if(someCondition)
                         {
                             throw new Exception("Error");
                         }

                         return t.Result; //This obviously does not work since we don't know the result. 
                    });

                return continuation;
            }


            private static async Task<string> CoreLogic()
            {
                return "test";
            }
        }
    }

表达问题的另一种方式。

  1. 我只能更改 DoExtraValidation() 中的内容。
  2. 我无法更改 DoExtraValidation() 的签名以使用泛型。

如何更改 DoExtraValidation 以使其适用于任何 Task 返回类型?

using System;
using System.Threading.Tasks;

namespace TaskContinueWith
{
    interface IServiceContract
    {
        Task<string> DoWork();
    }

    public class Servce : IServiceContract
    {
        public Task<string> DoWork()
        {
            var task = Task.FromResult("Hello");
            return  (Task<string>) DoExtraValidation(task);
        }

        private static Task DoExtraValidation(Task task)
        {
            Task returnTask = null;
            if (task.GetType() == typeof(Task<string>))
            {
                var knownType = task as Task<string>;
                returnTask = task.ContinueWith(
                    t =>
                    {
                        if(new Random().Next(100) > 50)
                        {
                            throw new Exception("Error");
                        }
                        return knownType.Result;

                    });
            }
            return returnTask;
        }
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            try
            {
                IServiceContract myService = new Servce();
                Task<string> myTask = myService.DoWork();
                myTask.Wait();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }

            Console.ReadLine();
        }
    }
}

【问题讨论】:

  • 为了让您的示例正常工作,unknownReturnType 任务必须是 Task&lt;string&gt;,因为这是您的 Interceptor() 方法返回的内容。总是Task&lt;string&gt; 吗?如果是这样,那么演员表将起作用。如果没有,那么您可以返回 Task ,然后客户端代码需要(以某种方式)弄清楚它返回了什么。但我的问题是——你为什么不知道返回的类型?最好对您正在尝试做的事情进行更好的描述 - 可能会有更好的解决方案。

标签: c# asynchronous task-parallel-library .net-4.5


【解决方案1】:

听起来像是dynamic 的案例。我会尽量少用dynamic。首先我们定义一个强类型助手:

static Task<TResult> SetContinuation<TResult>(Task<TResult> task)
{
    return task.ContinueWith(
        t =>
            {
             if(someCondition)
             {
                 throw new Exception("Error");
             }

             return t.Result;
        });
}

这个函数显然有效,但它需要知道TResultdynamic可以填写:

Task continuation = SetContinuation((dynamic)unknownReturnType);

我刚刚测试了绑定在运行时是否有效。或者,您可以使用反射来调用帮助程序(使用 MethodInfo.MakeGenericMethod 等)。

【讨论】:

  • 我做了一些更改,但似乎抛出了以下内容。 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:CallSite 的非静态字段、方法或属性“TaskContinueWith.Program.S etContinuation(System.Threading.Tasks.Task)”需要对象引用.Target(Closure , CallSite , Type , Object ) at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,Tret](CallSite 站点
  • 我将方法设为静态以修复错误。回答您的其他评论:关键是dynamic,它确实在内部使用了反射!它将在运行时填写类型参数。试试看。
  • 是的,它终于起作用了 :) 问题。这将是最快的。 1.使用动态调用SetContinuation。或 2. 使用 compileTimeGenerated MethodInfo 对象并调用调用?我认为2应该更快,不是吗?我问的原因是我可以使用 Resharper 的 compileTime 魔法为我生成一个通用方法,这样可以节省一些运行时间。
  • 不清楚您所说的“compileTimeGenerated MethodInfo”是什么意思。无论如何,动态会更快,因为它会在运行时发出一个专门处理给它的具体类型的方法。如果您在运行时只有很少的不同类型,这将非常快。 (如果需要,您可以手动构建它,但 DLR 会自动完成)。
  • 请记住,每个不同的任务子类型都需要不同的MethodInfo
【解决方案2】:

你可以使用泛型(拦截器在这里是一种扩展方法):

public static class ProjectExtensions
{
    public static Task<T> Interceptor<T>(this Task<T> task)
    {
        Task<T> continuation = task.ContinueWith<T>(t =>
            {
            if(someCondition)
            {
                throw new Exception("Error");
            }
            return t.Result;
        });
        return continuation;
    }
}

这样调用可以是任何类型的:

var t1 = new Task<string>(() => /* return string */).Interceptor();
var t2 = new Task<int>(() => /* return int */).Interceptor();

【讨论】:

  • 很抱歉。我移动了它。反正。我看不出这是如何工作的,因为它需要将一些类型信息传递给扩展方法。我没有这样的类型信息。我只能使用反射。我将如何使用反射调用此方法以允许类型信息通过?
  • @Tersius,此解决方案对于您给出的第二个示例是正确的(因为在该示例中调用 DoValidation 时,已知您正在处理任务)。如果您想要一个更好的示例,请向我们展示一个简洁但真实的示例,说明 DoExtraValidation 方法不知道类型信息的地方。
猜你喜欢
  • 2014-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多