31

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

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

// 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?我愿意接受任何建议。

4

5 回答 5

33

也许您可以Action<T>用作输出参数替代品

例子:

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
}
于 2013-08-08T04:06:22.370 回答
6

这是一个大约 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,所以也许这不是一个问题。你的旅费可能会改变。

于 2018-04-04T02:33:58.820 回答
2

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

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

它也摆脱了元组。

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

于 2013-08-08T03:33:06.190 回答
1

听起来像是泛型的问题。

public async Task<Tuple<bool, TResult>> TryBlah<TResult>(string key)
{
    var resultType = typeof(TResult);
    // ... perform some slow io, return new Tuple<bool, TResult>(...)
}
于 2013-08-08T03:42:02.273 回答
0

看起来您正在尝试创建一个 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);
于 2013-08-08T21:30:46.947 回答