2013-08-08 31 views
11

Szukam wskazówek, jak radzić sobie z poniższą sytuacją.Async Spróbuj (bla) wzór

Tworzę sposoby próbuje dostać w niektórych danych, w następstwie tego wzoru:

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

Zabrakło mi na problem, gdy próbuje się do tego wzoru na ich ASYNC wersje, ponieważ nie można używać out w metodach asynchronicznych:

// 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 
} 

Jednym z rozwiązań jest zwrócenie krotki zawierającej dane. Działa to w przypadku metod zwracających pojedynczy typ danych:

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

Problem polega na tym, że chcesz zwrócić różne typy danych. Bez użycia asynchronizacji można utworzyć kilka metod z niemal identycznymi podpisami, jak:

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

To świetnie. Właśnie tego chcę zrobić. Ten api jest bardzo prosty i łatwy w obsłudze (nazwy metod są takie same, tylko dane przekazywane w zmianach).

Nie można jednak użyć metody out przy pomocy metod asynchronicznych.

Jednym ze sposobów obejścia tego problemu jest zwrócenie Tuple swoich danych. Jednak teraz nie można mieć niemal identycznych podpisów metod, takich jak:

// 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) 
{ 
    // ... 
} 

Te metody nie działają, ponieważ mają te same podpisy. Jedynym sposobem, aby obejść ten, który przychodzi na myśl to, aby dać każdej metody odrębną nazwę, tak jak poniżej:

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

Mój problem jest to, że teraz tworzy co uważam za paskudny api gdzie masz teraz dużo różnych metod. Tak, to nie jest tak duży problem, ale czuję, że musi być lepszy sposób.

Czy istnieją inne wzorce, które nadają się do ładniejszego interfejsu API podczas pracy z takimi asynchronicznymi metodami? Jestem otwarty na wszelkie sugestie.

Odpowiedz

1

Nie użyłbym metody Try * z TPL. Zamiast tego użyj kontynuacji (Task.ContinueWith) z opcjami OnlyOnFaulted.

ten sposób zadanie zakończy się ten czy inny sposób i rozmówca staje się zdecydować, w jaki sposób obsługi błędów, odwołania itp

również pozbywa krotki.

Jeśli chodzi o inne problemy z projektowaniem, w każdej chwili widzę, jak ktoś mówi: "Chcę, aby ta metoda przeciążała się na podstawie typu zwrotu" Czuję zły pomysł. Wolałbym widzieć pełne nazwy (GetString, GetByte, GetByteArray itp. - spójrz na SqlDataReader) lub poproś API o bardzo podstawowy typ (np. Bajt [] - spójrz na Stream) i pozwól dzwoniącemu tworzyć konwersje na wyższym poziomie, takie jak StreamReader/TextReader/etc.

+3

Czy czuję Promises in C#? – cgatian

2

Brzmi jak problem dla leków generycznych.

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

Zastanowiłem się nad sugestią tego, ale biorąc pod uwagę, że wydaje się on być zaniepokojony "nieprzyjemnym API", nie jestem pewny, że jest wiele przewagi nad odmiennymi nazwanymi metodami, które pierwotnie sugerował OP - w rzeczywistości trzeba wpisać 2 dodatkowe znaki: < and > :) Przypuszczam, że z punktu widzenia tworzenia tych metod może być coś pożytecznego ... – mutex

+0

@mutex Cóż, poza tym, rozwiązując początkowy problem, jakim jest możliwość (w zasadzie) przeciążenia na typie powrotu, może być również lepszym rozwiązaniem do bardziej ogólnego problemu związanego z operowaniem tą samą operacją na kilku różnych typach danych. –

+0

Dużym problemem jest to, że nie ma wskazania, które typy są dozwolone. Idealnie powinno się stosować generyczne metody, które mogą współpracować z dowolnym typem, a nie tylko z pół tuzinem konkretnych typów. – svick

7

Być może można użyć Action<T> jako substytut się param

Przykład:

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

Zastosowanie:

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

To jest całkiem fantastyczne –

0

Wygląda na to, że staramy się API, które pobiera żądanie, a następnie pobiera niektóre dane, a następnie przetwarza/konwertuje te dane w określony sposób i zwraca go z powrotem do rozmówcy. Co jeśli zaimplementowałeś menedżera, który obsługiwałby różne metody przetwarzania.

Proponuję rozwiązanie tworzenia klasy żądania i odpowiedzi, która zostanie przekazana menedżerowi, a następnie menedżer zwróci wynik po zakończeniu przetwarzania.

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

Najważniejsze jest to, że słownik (lub HashSet) zawiera dostępne procesory. Następnie w oparciu o typ, wywoływany jest odpowiedni procesor, a wyniki są zwracane.

Kod zostanie wywołany w następujący sposób.