2017-12-30 259 views
13

mam klasę statyczną pełne metod rozszerzenie gdzie każda z metod jest asynchroniczna i zwraca jakąś wartość - tak:Obsada Zadanie <T> do zadania <object> w C# bez konieczności T

public static class MyContextExtensions{ 
    public static async Task<bool> SomeFunction(this DbContext myContext){ 
    bool output = false; 
    //...doing stuff with myContext 
    return output; 
    } 

    public static async Task<List<string>> SomeOtherFunction(this DbContext myContext){ 
    List<string> output = new List<string>(); 
    //...doing stuff with myContext 
    return output; 
    } 
} 

Moim celem jest, aby móc aby wywołać którąkolwiek z tych metod z jednej metody w innej klasie i zwrócić ich wynik jako obiekt. Wyglądałoby to mniej więcej tak:

public class MyHub: Hub{ 
    public async Task<object> InvokeContextExtension(string methodName){ 
    using(var context = new DbContext()){ 
     //This fails because of invalid cast 
     return await (Task<object>)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context); 
    } 
    } 
} 

Problem polega na tym, że obsada kończy się niepowodzeniem. Mój dylemat polega na tym, że nie mogę przekazać żadnych parametrów typu do metody "InvokeContextExtension", ponieważ jest ona częścią koncentratora SignalR i jest wywoływana przez javascript. I do pewnego stopnia nie dbam o metodę zwracania metody rozszerzenia, ponieważ zostanie ona tylko spersonalizowana do JSON i odesłana do klienta javascript. Muszę jednak rzucić wartość zwróconą przez Invoke jako zadanie, aby użyć operatora oczekującego. I muszę podać ogólny parametr z tym "Zadaniem", w przeciwnym razie potraktuje on typ zwrotu jako nieważny. Wszystko sprowadza się do tego, jak pomyślnie rzucić zadanie z ogólnym parametrem T do zadania z ogólnym parametrem obiektu, w którym T reprezentuje wynik metody rozszerzenia.

+2

Dlaczego nie użyć klasy bazowej, 'Zadanie'? Będziesz musiał zrobić refleksję, aby i tak uzyskać wynik ponownie. Albo napisz jakąś metodę, by opisać różnice dla ciebie: 'async Zadanie GetResult (Zadanie Zadanie) {return czekanie na zadanie; } ' –

Odpowiedz

6

Można to zrobić w dwóch etapach - await zadanie przy użyciu klasy bazowej, a następnie zebrać wynik przy użyciu odbicia lub dynamic:

using(var context = new DbContext()) { 
    // Get the task 
    Task task = (Task)typeof(MyContextExtensions).GetMethod(methodName).Invoke(null, context); 
    // Make sure it runs to completion 
    await task.ConfigureAwait(false); 
    // Harvest the result 
    return (object)((dynamic)task).Result; 
} 

Powyżej znajduje się pełna uruchomiony przykład, który stawia w kontekście wyżej technika nazywając Task poprzez refleksję:

class MainClass { 
    public static void Main(string[] args) { 
     var t1 = Task.Run(async() => Console.WriteLine(await Bar("Foo1"))); 
     var t2 = Task.Run(async() => Console.WriteLine(await Bar("Foo2"))); 
     Task.WaitAll(t1, t2); 
    } 
    public static async Task<object> Bar(string name) { 
     Task t = (Task)typeof(MainClass).GetMethod(name).Invoke(null, new object[] { "bar" }); 
     await t.ConfigureAwait(false); 
     return (object)((dynamic)t).Result; 
    } 
    public static Task<string> Foo1(string s) { 
     return Task.FromResult("hello"); 
    } 
    public static Task<bool> Foo2(string s) { 
     return Task.FromResult(true); 
    } 
} 
10

Ogólnie konwertować Task<T> do Task<object>, chciałbym po prostu przejść do prostego odwzorowania kontynuacja:

Task<T> yourTaskT; 

// .... 

Task<object> yourTaskObject = yourTaskT.ContinueWith(t => (object) t.Result); 

(documentation link here)


Jednak rzeczywisty specyficzne potrzeby jest , aby wywołać Task przez odbicie i uzyskać jego (nieznany typ) wynik.

W tym celu można zapoznać się z kompletnym dasblinkenlight's answer, który powinien pasować do konkretnego problemu.

+0

Problem polega na tym, że wywołuje zadanie poprzez odbicie, więc nie ma" T "i nie może wykonać' twojegoTaskT'. Najlepsze, co może zrobić, to 'Task', ale wtedy nie będzie w stanie zrobić' t.Result' wewnątrz 'ContinueWith'. – dasblinkenlight

+0

tak. Niestety, wydaje mi się, że skupiłem się zbytnio na tytule pytania ogólnego, a nie na rzeczywistym problemie PO. – Pac0

+0

Zgadzam się, tytuł i pytanie nie są ze sobą idealnie dopasowane. Tytuł jest prosty, ale pytanie ma ważne znaczenie. Chociaż twoja odpowiedź może nie rozwiązać problemu OP, jest idealnie dopasowana do bieżącego tytułu. Nie usunąłbym go - zamiast tego edytowałbym, aby dodać drugą część, która pokazuje, jak wywołać zadanie o nieznanym typie wyniku poprzez odbicie. – dasblinkenlight

2

nie można rzucać Task<T> do Task<object>, ponieważ nie jest kowariantna Task<T> (to nie kontrawariantny, eith er). Najprostszym rozwiązaniem byłoby użycie większej ilości odbić:

var task = (Task) mi.Invoke (obj, null) ; 
var result = task.GetType().GetProperty ("Result").GetValue (task) ; 

Jest to powolne i nieefektywne, ale użyteczne, jeśli ten kod nie jest często wykonywany. Na marginesie, jaki jest pożytek z posiadania asynchronicznej metody MakeMyClass1, jeśli zamierzasz zablokować czekanie na jej wynik?

i Inną możliwością jest napisać metodę rozszerzenia do tego celu:

public static Task<object> Convert<T>(this Task<T> task) 
    { 
     TaskCompletionSource<object> res = new TaskCompletionSource<object>(); 

     return task.ContinueWith(t => 
     { 
      if (t.IsCanceled) 
      { 
       res.TrySetCanceled(); 
      } 
      else if (t.IsFaulted) 
      { 
       res.TrySetException(t.Exception); 
      } 
      else 
      { 
       res.TrySetResult(t.Result); 
      } 
      return res.Task; 
     } 
     , TaskContinuationOptions.ExecuteSynchronously).Unwrap(); 
    } 

To żaden blokowania rozwiązanie i będzie zachować oryginalny stan/wyjątek zadania.

+1

Podczas gdy twoje pierwsze rozwiązanie na pewno zadziała, OP nie będzie mógł skorzystać z twojego drugiego rozwiązania, ponieważ nie ma zadania 'T' dla' tego zadania zadania. – dasblinkenlight

+0

@ dasblinkenlight to tylko dla przyszłości :) może ktoś jeszcze przychodzi w tym momencie i potrzebuje tego. Chciałem tylko uzyskać kompletną odpowiedź. oczywiście powiedziałem "Inna możliwość". dziękuję za odpowiedź duetu. –

+1

To jest dobre rozwiązanie, ponieważ zachowuje stan anulowania. Jeśli nie przejmowałeś się tym, to 'public static async Zadanie Konwertuj (to zadanie t) => czeka na t;' jest ładnie zwięzłe. –

0

To nie jest dobry pomysł, aby mieszać await z dynamicznym/refleksji powołać się od await jest instrukcja kompilator, który generuje dużo kodu wokół wywoływanego metoda i nie ma prawdziwego sensu „emulować” pracy kompilatora z większą odbicia, kontynuacje , opakowania itp.

Od tego, co potrzebujesz, aby zarządzać swoim kodem w CZASIE PRACY, wtedy zapomnij o składniowym cukrze asyc await, który działa podczas kompilacji. Bez nich przepisz SomeFunction i SomeOtherFunction i rozpocznij operacje we własnych zadaniach utworzonych w czasie wykonywania. Otrzymasz to samo zachowanie, ale z krystalicznie czystym kodem.

+0

Doceniam twój wgląd i widzę, gdzie byłoby to idealne wdrożenie w wielu sytuacjach. Niestety, w tej chwili nie jest to dla mnie praktyczne rozwiązanie, ponieważ wzorzec asynchroniczny został już ustalony i miałby znaczny wpływ na wiele projektów i chciałbym mieć niezadowolonych członków zespołu, gdyby go teraz zmienili. – ncarriker

0

Najskuteczniejszym rozwiązaniem byłoby zwyczaj awaiter:

struct TaskCast<TSource, TDestination> 
    where TSource : TDestination 
{ 
    readonly Task<TSource> task; 

    public TaskCast(Task<TSource> task) 
    { 
     this.task = task; 
    } 

    public Awaiter GetAwaiter() => new Awaiter(task); 

    public struct Awaiter 
     : System.Runtime.CompilerServices.INotifyCompletion 
    { 
     System.Runtime.CompilerServices.TaskAwaiter<TSource> awaiter; 

     public Awaiter(Task<TSource> task) 
     { 
      awaiter = task.GetAwaiter(); 
     } 

     public bool IsCompleted => awaiter.IsCompleted;  
     public TDestination GetResult() => awaiter.GetResult();  
     public void OnCompleted(Action continuation) => awaiter.OnCompleted(continuation); 
    } 
} 

z poniższej rejestracji:

Task<...> someTask = ...; 
await TaskCast<..., object>(someTask); 

Ograniczeniem tej metody jest to, że wynik nie jest Task<object> ale awaitable przedmiot.