【问题标题】:Async Try(blah) pattern [duplicate]异步 Try(blah) 模式 [重复]
【发布时间】:2013-08-09 15:36:12
【问题描述】:

我正在寻找有关如何处理以下情况的建议。

我正在创建方法来尝试获取一些数据,遵循以下模式:

// Typical pattern
public bool TryBlah(string key, out object value)
{
    // ... set value and return boolean
}

我在尝试在他们的异步版本上遵循此模式时遇到了一个问题,因为您不能在异步方法上使用 out

// Ideal async pattern (not allowed to use an 'out' parameter, so this fails)
public async Task<bool> TryBlah(string key, out object value)
{
    // ... set value, perform some slow io operation, return bool
}

一种解决方法是返回一个包含您的数据的元组。这适用于返回单一数据类型的方法,如下所示:

// Tuple version
public async Task<Tuple<bool, object>> TryBlah(string key)
{
    // ... perform some slow io, return new Tuple<bool, object>(...)
}

问题是当您想要返回不同的数据类型时。如果不使用 async,您可以创建几个具有几乎相同签名的方法,如下所示:

public bool TryBlah(string key, out byte[] value)
{
    // ...
}
public bool TryBlah(string key, out string value)
{
    // ...
}

那太好了。这就是我想要做的。这个api非常简单易用(方法名都是一样的,只是传入的数据有变化)。

虽然不能将out 与异步方法一起使用,但这会造成混乱。

解决此问题的一种方法是返回数据的Tuple。但是,现在您不能拥有几乎相同的方法签名,如下所示:

// The suck... the signatures match, but you want to return different values.
// You can't do this:
public async Task<Tuple<bool, byte[]>> TryBlah(string key)
{
    // ...
}
public async Task<Tuple<bool, string>> TryBlah(string key)
{
    // ...
}

这些方法失败是因为它们具有相同的签名。解决这个问题的唯一方法是给每个方法一个不同的名称,如下所示:

public async Task<Tuple<bool, byte[]>> TryBlahByteArray(string key)
{
    // ...
}
public async Task<Tuple<bool, string>> TryBlahString(string key)
{
    // ...
}

我的问题是,这现在创建了我认为令人讨厌的 api,您现在有很多不同的方法。是的,这不是什么大问题,但我觉得必须有更好的方法。

在使用这样的异步方法时,是否还有其他模式可以提供更好的 api?我愿意接受任何建议。

【问题讨论】:

    标签: c# async-await


    【解决方案1】:

    这是一个大约 2017 年更新的 ValueTuples,你的糟糕选择还不错。

    public async Task<(bool, byte[])> TryBlahByteArray(string key)
    {
        // await something
        return (true, new byte[1]);
    }
    public async Task<(bool, string)> TryBlahString(string key)
    {
        // await something
        return (false, "blah");
    }
    

    用作

    (bool success, byte[] blahs) = await TryBlahByteArray("key");
    

    还有

    (bool success, string blah) = await TryBlahString("key");
    

    我不经常想要返回不同内容的相同方法名称或原始object,所以也许这不是一个问题。您的里程可能会有所不同。

    【讨论】:

    • 在 C# 8 中,这可以进一步缩短:使用if (await TryBlahString("foo") is (true, var result)),变量result 将是string,并且代码路径将仅使用第一个值输入元组是true
    【解决方案2】:

    看起来您正在尝试创建一个 API,该 API 接收请求,然后检索一些数据,然后以特定方式处理/转换该数据并将其返回给调用者。如果您实现了一个可以处理不同处理方法的管理器会怎样。

    我提出了一个解决方案,创建一个请求和响应类,将传递给管理器,然后管理器在处理完成后返回结果。

    public class Request
    {
        public Type ReturnType;
        public string Key { get; set; }
        public Request(string Key, Type returnType)
        {
            this.Key = Key;
            this.ReturnType = returnType;
        }
    }
    
    public class Response
    {
        public object value;
        public Type returnType;
    }
    
    //Singleton processor to get data out of cache
    public class CacheProcessor
    {
        private static CacheProcessor instance;
    
        public static CacheProcessor Process
        {
            get
            {
                if (instance == null)
                    instance = new CacheProcessor();
                return instance;
            }
        }
    
        private Dictionary<Type, Func<Request, object>> Processors = new Dictionary<Type, Func<Request, object>>();
    
        private CacheProcessor()
        {
            CreateAvailableProcessors(); 
        }
    
        //All available processors available here
        //You could change type to string or some other type 
        //to extend if you need something like "CrazyZipUtility" as a processor
        private void CreateAvailableProcessors()
        {
            Processors.Add(typeof(string), ProcessString);
            Processors.Add(typeof(byte[]), ProcessByteArry);   
        }
    
        //Fake method, this should encapsulate all crazy 
        //cache code to retrieve stuff out
        private static string CacheGetKey(string p)
        {
            return "1dimd09823f02mf23f23f0";  //Bullshit data
        }
    
        //The goood old tryBlah... So Sexy
        public Response TryBlah(Request request)
        {
            if (Processors.ContainsKey(request.ReturnType))
            {
                object processedObject = Processors[request.ReturnType].Invoke(request);
                return new Response()
                {
                    returnType = request.ReturnType,
                    value = processedObject
                };
            }
            return null;
        }
    
        //Maybe put these in their own class along with the dictionary
        //So you can maintain them in their own file
        private static object ProcessString(Request request)
        {
            var value = CacheGetKey(request.Key);
            //Do some shit
            return value;
        }
    
        private static object ProcessByteArry(Request request)
        {
            var value = CacheGetKey(request.Key);
            ASCIIEncoding encoding = new ASCIIEncoding();
            Byte[] bytes = encoding.GetBytes(value);
            return bytes;
        }
    }
    

    最重要的是 Dictionary(或 HashSet)拥有您可用的处理器。然后根据类型调用正确的处理器并返回结果。

    代码将被调用如下。

    var makeByteRequest = new Request("SomeValue", typeof(byte[]));
    Response btyeResponse = CacheProcessor.Process.TryBlah(makeByteRequest);
    

    【讨论】:

      【解决方案3】:

      也许你可以使用Action&lt;T&gt; 作为输出参数替代

      例子:

      public async Task<bool> TryBlah(string key, Action<int> value)
      {
          int something = await DoLongRunningIO();
          value(something)
          return true;         
      }
      

      用法:

      int myOutParam = 0;
      if (await TryBlah("Something", value => myOutParam = value))
      {
          // do somthing
      }
      

      【讨论】:

      • 这太棒了
      【解决方案4】:

      听起来像是泛型的问题。

      public async Task<Tuple<bool, TResult>> TryBlah<TResult>(string key)
      {
          var resultType = typeof(TResult);
          // ... perform some slow io, return new Tuple<bool, TResult>(...)
      }
      

      【讨论】:

      • 我考虑过提出这个建议,但鉴于他似乎关心“讨厌的 API”,我不确定与 OP 最初建议的不同命名方法相比有多大优势 - 事实上你必须输入2 个额外的字符: :) 我想从编写这些方法的角度来看可能会有优势......
      • @mutex 好吧,除了它解决了能够(本质上)重载返回类型的初始问题之外,它也可能是解决必须处理相同操作的更普遍问题的更好解决方案跨越几种不同的数据类型。
      • 最大的问题是没有指明允许哪些类型。理想情况下,泛型应该用于可以与任何类型一起使用的方法,而不仅仅是六种特定类型。
      • @svick 你是对的,但请注意这个方法提供了一个布尔结果,如果它被一个意外的类型调用,它将允许它优雅地失败(而不是抛出)。
      【解决方案5】:

      我不会在 TPL 中使用 Try* 方法。而是使用带有 OnlyOnFaulted 选项的延续 (Task.ContinueWith)。

      这样,您的任务以一种或另一种方式完成,调用者可以决定如何处理错误、取消等。

      它也摆脱了元组。

      至于其他设计问题,每当我看到有人说“我希望此方法根据返回类型重载”时,我都觉得这是个坏主意。我宁愿看到详细的名称(GetString、GetByte、GetByteArray 等 - 查看 SqlDataReader)或让 API 返回一个非常基本的类型(例如 byte[] - 查看 Stream)并让调用者创建更高级别的转换,如 StreamReader /TextReader/等。

      【讨论】:

      • 我在 C# 中闻到了 Promise 的味道吗?
      • 我认为这可能是一个非常优雅的方法。
      猜你喜欢
      • 2011-09-16
      • 1970-01-01
      • 2013-12-05
      • 2014-07-16
      • 2015-07-13
      • 2021-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多