Comment exécuter une méthode asynchrone de tâche <T> de manière synchrone?


628

J'apprends sur async / wait et j'ai rencontré une situation où je dois appeler une méthode async de manière synchrone. Comment puis je faire ça?

Méthode asynchrone:

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

Utilisation normale:

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

J'ai essayé d'utiliser les éléments suivants:

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

J'ai également essayé une suggestion d' ici , mais elle ne fonctionne pas lorsque le répartiteur est suspendu.

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

Voici l'exception et la trace de pile de l'appel RunSynchronously:

System.InvalidOperationException

Message : RunSynchronously ne peut pas être appelé sur une tâche non liée à un délégué.

InnerException : null

Source : mscorlib

StackTrace :

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()

46
La meilleure réponse à la question «Comment puis-je appeler une méthode asynchrone de manière synchrone» est «ne pas». Il y a des hacks pour essayer de le forcer à fonctionner, mais ils ont tous des pièges très subtils. Au lieu de cela, sauvegardez et corrigez le code qui vous rend "nécessaire" pour ce faire.
Stephen Cleary

58
@Stephen Cleary Tout à fait d'accord, mais parfois c'est tout simplement inévitable, comme lorsque votre code dépend d'une API tierce qui n'utilise pas async / wait. De plus, s'il est lié aux propriétés WPF lors de l'utilisation de MVVM, il est littéralement impossible d'utiliser async / wait car cela n'est pas pris en charge sur les propriétés.
Contango

3
@StephenCleary Pas toujours. Je crée une DLL qui sera importée dans GeneXus . Il ne prend pas en charge les mots clés asynchrones / en attente, je ne dois donc utiliser que des méthodes synchrones.
Dinei

5
@StephenCleary 1) GeneXus est un 3ème outil pt et je n'ai pas accès à son code source; 2) GeneXus n'a même pas d'implémentation de "fonctions", donc je ne peux pas réaliser comment je pourrais implémenter un "rappel" avec ce type de chose. Ce serait sûrement une solution de contournement plus difficile que l'utilisation Tasksynchrone; 3) J'intègre GeneXus avec le pilote MongoDB C # , qui expose certaines méthodes uniquement de manière asynchrone
Dinei

1
@ygoe: utilisez un verrou compatible asynchrone, tel que SemaphoreSlim.
Stephen Cleary

Réponses:


456

Voici une solution de contournement que j'ai trouvée qui fonctionne pour tous les cas (y compris les répartiteurs suspendus). Ce n'est pas mon code et je travaille toujours pour bien le comprendre, mais ça marche.

Il peut être appelé en utilisant:

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

Le code est d' ici

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}

28
Pour un aperçu de la façon dont cela fonctionne, Stephen Toub (M. Parallel) a écrit une série de messages à ce sujet. Partie 1 Partie 2 Partie 3
Cameron MacFarland

18
J'ai mis à jour le code de John pour qu'il fonctionne sans encapsuler les tâches dans lambdas: github.com/tejacques/AsyncBridge . Essentiellement, vous travaillez avec des blocs asynchrones avec l'instruction using. Tout ce qui se trouve dans un bloc using se produit de manière asynchrone, avec une attente à la fin. L'inconvénient est que vous devez déballer la tâche vous-même dans un rappel, mais c'est toujours assez élégant, surtout si vous devez appeler plusieurs fonctions asynchrones à la fois.
Tom Jacques

17
@StephenCleary Bien que je sois généralement d'accord avec vous que le code doit être asynchrone jusqu'en bas, parfois vous vous retrouvez dans une situation infaisable où il faut le forcer comme un appel synchrone. Fondamentalement, ma situation est que tout mon code d'accès aux données est en mode asynchrone. J'avais besoin de créer un plan du site basé sur le plan du site et la bibliothèque tierce que j'utilisais était MvcSitemap. Maintenant, quand on l'étend via la DynamicNodeProviderBaseclasse de base, on ne peut pas le déclarer comme asyncméthode. Soit je devais remplacer par une nouvelle bibliothèque, soit simplement appeler un op synchrone.
justin.lovell

6
@ justin.lovell: Oui, les limitations de la bibliothèque peuvent nous obliger à mettre en place des hacks, au moins jusqu'à ce que la bibliothèque soit mise à jour. On dirait que MvcSitemap est une de ces situations où un hack est requis (filtres MVC et actions enfants aussi); Je dissuade simplement les gens de cela en général parce que des hacks comme celui-ci sont utilisés trop souvent lorsqu'ils ne sont pas nécessaires. Avec MVC en particulier, certaines API ASP.NET/MVC supposent qu'elles ont un AspNetSynchronizationContext, donc ce hack particulier ne fonctionnera pas si vous appelez ces API.
Stephen Cleary

5
Ce code ne fonctionnera pas. S'il est appelé à partir d'un thread de pool, il peut déclencher un blocage de famine de thread. Votre appelant bloquera l'attente de la fin de l'opération, ce qui peut ne jamais se produire s'il a épuisé le pool de threads. Voir cet article .
ZunTzu du

318

Sachez que cette réponse a trois ans. Je l'ai écrit principalement basé sur une expérience avec .Net 4.0, et très peu avec 4.5 en particulier avec async-await. D'une manière générale, c'est une belle solution simple, mais cela casse parfois les choses. Veuillez lire la discussion dans les commentaires.

.Net 4.5

Utilisez simplement ceci:

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

Voir: TaskAwaiter , Task.Result , Task.RunSynchronously


.Net 4.0

Utilisez ceci:

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

...ou ca:

task.Start();
task.Wait();

67
.Resultpeut produire une impasse dans certains scénarios
Jordy Langen

122
Resultpeut facilement provoquer un blocage dans le asynccode , comme je le décris sur mon blog.
Stephen Cleary

8
@StephenCleary J'ai lu votre message et l'ai essayé moi-même. Honnêtement, je pense que quelqu'un chez Microsoft était vraiment ivre ... C'est le même problème que les winforms et les threads d'arrière-plan ....
AK_

9
La question concerne une tâche renvoyée par la méthode async. Ce type de tâche peut avoir déjà été démarré, exécuté ou annulé, donc l'utilisation de la méthode Task.RunSynchronously peut entraîner une InvalidOperationException . Voir page MSDN: méthode Task.RunSynchronously . En outre, cette tâche est probablement créée par les méthodes Task.Factory.StartNew ou Task.Run (à l'intérieur de la méthode async), il est donc dangereux d'essayer de la redémarrer. Certaines conditions de course peuvent se produire lors de l'exécution. En revanche, Task.Wait et Task.Result peuvent entraîner un blocage.
sgnsajgon

4
Run Synchronously a travaillé pour moi ... Je ne sais pas si je manque quelque chose, mais cela semble préférable aux horreurs de la réponse marquée - je cherchais simplement un moyen de désactiver async pour tester le code juste là pour arrêter l'
interface utilisateur

121

Surpris, personne n'a mentionné cela:

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

Pas aussi joli que certaines des autres méthodes ici, mais il présente les avantages suivants:

  • il n'avale pas d'exceptions (comme Wait)
  • il n'encapsulera aucune exception levée dans un AggregateException(comme Result)
  • fonctionne pour les deux Tasket Task<T>( essayez-le vous-même! )

De plus, comme il GetAwaiterest de type canard, cela devrait fonctionner pour tout objet renvoyé par une méthode asynchrone (comme ConfiguredAwaitableou YieldAwaitable), pas seulement pour les tâches.


edit: Veuillez noter qu'il est possible que cette approche (ou utilisation .Result) se bloque, sauf si vous vous assurez d'ajouter à .ConfigureAwait(false)chaque fois que vous attendez, pour toutes les méthodes asynchrones qui peuvent éventuellement être atteintes à partir de BlahAsync()(pas seulement celles qu'elle appelle directement). Explication .

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

Si vous êtes trop paresseux pour ajouter .ConfigureAwait(false)partout et que vous ne vous souciez pas des performances, vous pouvez également le faire

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()

1
Fonctionne pour moi pour des trucs simples. De plus, si la méthode renvoie une IAsyncOperation, je devais d'abord la convertir en tâche: BlahAsync (). AsTask (). GetAwaiter (). GetResult ();
Lee McPherson

3
Cela a provoqué un blocage dans une méthode Web asmx. Néanmoins, l'encapsulation de l'appel de méthode dans un Task.Run () l'a fait fonctionner: Task.Run (() => BlahAsync ()). GetAwaiter (). GetResult ()
Augusto Barreto

J'aime mieux cette approche syntaxiquement car elle n'implique pas de lambdas.
dythim

25
Veuillez NE PAS modifier les réponses des autres pour insérer un lien vers la vôtre. Si vous pensez que votre réponse est meilleure, laissez-la plutôt comme commentaire.
Rachel

1
docs.microsoft.com/en-us/dotnet/api/… dit à propos de GetAwaiter(): "Cette méthode est destinée à l'utilisateur du compilateur plutôt qu'à utiliser directement dans le code."
Theophilus

75

Il est beaucoup plus simple d'exécuter la tâche sur le pool de threads, plutôt que d'essayer de tromper le planificateur pour l'exécuter de manière synchrone. De cette façon, vous pouvez être sûr qu'il ne se bloquera pas. Les performances sont affectées en raison du changement de contexte.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 

3
Ensuite, vous appelez task.Wait (). Le type de données est simplement Tâche.
Michael L Perry

1
Supposons que DoSomethingAsync () est une méthode asynchrone de longue durée dans son ensemble (en interne, elle attend une tâche de longue durée), mais elle renvoie rapidement un contrôle de flux à son appelant, ainsi le travail de l'argument lambda se termine également rapidement. Le résultat de Tusk.Run () peut Tâche <Tâche> ou Tâche <Tâche <>> , donc vous attendez un résultat de tâche externe qui se termine rapidement, mais une tâche interne (en raison de l'attente d'un travail de longue durée dans la méthode async) est toujours en cours d'exécution. Les conclusions sont que nous devons probablement utiliser l' approche Unwrap () (comme cela a été fait dans @ J.Lennon post) pour obtenir un comportement synchrone de la méthode asynchrone.
sgnsajgon

5
@sgnsajgon Vous vous trompez. Task.Run est différent de Task.Factory.StartNew en ce qu'il déballe déjà automatiquement le résultat. Voir cet article .
ZunTzu

1
Puis-je simplement écrire à la Task.Run(DoSomethingAsync)place? Cela supprime un niveau de délégués.
ygoe

1
Oui. Aller dans la direction opposée, cependant, comme dans Task<MyResult> task = Task.Run(async () => await DoSomethingAsync());est plus explicite et répond au souci de @sgnsajgon qu'il pourrait renvoyer une tâche <Task <MyResult>>. La surcharge correcte de Task.Run est sélectionnée dans les deux cas, mais le délégué asynchrone rend votre intention évidente.
Michael L Perry

57

J'apprends sur async / wait et j'ai rencontré une situation où je dois appeler une méthode async de manière synchrone. Comment puis je faire ça?

La meilleure réponse est que non , les détails dépendant de la "situation".

Est-ce un getter / setter de propriété? Dans la plupart des cas, il vaut mieux avoir des méthodes asynchrones que des "propriétés asynchrones". (Pour plus d'informations, consultez mon article de blog sur les propriétés asynchrones ).

S'agit-il d'une application MVVM et vous souhaitez effectuer une liaison de données asynchrone? Utilisez ensuite quelque chose comme le mien NotifyTask, comme décrit dans mon article MSDN sur la liaison de données asynchrone .

Est-ce un constructeur? Ensuite, vous voudrez probablement envisager une méthode d'usine asynchrone. (Pour plus d'informations, consultez mon article de blog sur les constructeurs asynchrones ).

Il y a presque toujours une meilleure réponse que de faire une synchronisation sur async.

Si ce n'est pas possible pour votre situation (et vous le savez en posant ici une question décrivant la situation ), je vous recommanderais simplement d'utiliser du code synchrone. Il est préférable d’asynchroniser complètement; la synchronisation complète est la deuxième meilleure option. La synchronisation sur async n'est pas recommandée.

Cependant, il existe une poignée de situations où la synchronisation sur async est nécessaire. Plus précisément, vous êtes contraint par le code appelant de sorte que vous devez être synchronisé (et que vous n'avez absolument aucun moyen de repenser ou de restructurer votre code pour autoriser l'asynchronie), et vous devez appeler le code asynchrone. C'est une situation très rare, mais elle revient de temps en temps.

Dans ce cas, vous devrez utiliser l'un des hacks décrits dans mon article sur le développement des friches industriellesasync , en particulier:

  • Blocage (par exemple, GetAwaiter().GetResult()). Notez que cela peut provoquer des blocages (comme je le décris sur mon blog).
  • Exécution du code sur un thread de pool de threads (par exemple, Task.Run(..).GetAwaiter().GetResult()). Notez que cela ne fonctionnera que si le code asynchrone peut être exécuté sur un thread de pool de threads (c'est-à-dire qu'il ne dépend pas d'une interface utilisateur ou d'un contexte ASP.NET).
  • Boucles de messages imbriquées. Notez que cela ne fonctionnera que si le code asynchrone suppose uniquement un contexte à thread unique, pas un type de contexte spécifique (beaucoup de code UI et ASP.NET attendent un contexte spécifique).

Les boucles de messages imbriquées sont les plus dangereuses de tous les hacks, car elles provoquent une nouvelle entrée . La ré-entrée est extrêmement difficile à raisonner et (IMO) est la cause de la plupart des bogues d'application sous Windows. En particulier, si vous êtes sur le thread d'interface utilisateur et que vous bloquez sur une file d'attente de travail (en attendant que le travail asynchrone se termine), le CLR effectue en fait un pompage de messages pour vous - il va réellement gérer certains messages Win32 à partir de votre code . Oh, et vous n'avez aucune idée de quels messages - quand Chris Brumme dit "Ce ne serait pas génial de savoir exactement ce qui sera pompé? Malheureusement, le pompage est un art noir qui est au-delà de la compréhension mortelle." , alors nous n'avons vraiment aucun espoir de savoir.

Donc, lorsque vous bloquez comme ça sur un thread d'interface utilisateur, vous demandez des ennuis. Une autre cbrumme cite du même article: "De temps en temps, les clients à l'intérieur ou à l'extérieur de l'entreprise découvrent que nous pompons des messages lors du blocage géré sur un STA [thread d'interface utilisateur]. C'est une préoccupation légitime, car ils savent que c'est très difficile pour écrire du code robuste face à la réentrance. "

Oui, ça l'est. Très difficile à écrire du code robuste face à la réentrance. Et les boucles de messages imbriquées vous obligent à écrire du code robuste face à la réentrance. C'est pourquoi la réponse acceptée (et la plus votée) à cette question est extrêmement dangereuse dans la pratique.

Si vous êtes complètement hors de toutes les autres options - vous ne pouvez pas reconcevoir votre code, vous ne pouvez pas le restructurer pour qu'il soit asynchrone - vous êtes obligé par le code d'appel immuable d'être synchronisé - vous ne pouvez pas changer le code en aval pour qu'il soit synchronisé - vous ne pouvez pas bloquer - vous ne pouvez pas exécuter le code asynchrone sur un thread séparé - alors et seulement alors si vous envisagez d'adopter la réentrance.

Si vous vous trouvez dans ce coin, je recommanderais d'utiliser quelque chose comme Dispatcher.PushFramepour les applications WPF , en boucle avec Application.DoEventspour les applications WinForm et pour le cas général, le mien AsyncContext.Run.


Stephen, il y a un autre très similaire qestion qui vous avez fourni trop réponse impressionnante. Pensez-vous que l'un d'eux peut être fermé en double ou peut-être fusionner la demande ou faire apparaître la méta en premier (car chaque q a ~ 200 000 vues 200+ votes)? Suggestions?
Alexei Levenkov

1
@AlexeiLevenkov: Je ne me sens pas bien de le faire, pour plusieurs raisons: 1) La réponse à la question liée est assez obsolète. 2) J'ai écrit un article entier sur le sujet qui me semble plus complet que tout autre SO Q / A existant. 3) La réponse acceptée à cette question est extrêmement populaire. 4) Je m'oppose avec véhémence à cette réponse acceptée. Donc, fermer cela comme un dupe serait un abus de pouvoir; fermer cela en tant que dupe de cela (ou fusionner) donnerait encore plus de pouvoir à une réponse dangereuse. Je le laisse faire et je laisse ça à la communauté.
Stephen Cleary

D'accord. Je vais envisager de le mettre sur méta que d'une certaine manière.
Alexei Levenkov

9
Cette réponse me dépasse largement la tête. "Utiliser async jusqu'au bout" est un conseil déroutant, car il est clairement impossible de suivre. Un programme avec une Main()méthode async ne compile pas; à un moment donné que vous avez obtenu à combler le fossé entre les mondes de synchronisation et async. Ce n'est pas une " situation très rare" , c'est nécessaire dans littéralement tous les programmes qui appellent une méthode asynchrone. Il n'y a pas d'option pour ne pas "faire de synchronisation sur async" , juste une option pour shunter ce fardeau jusqu'à la méthode appelante au lieu de l'assumer dans celle que vous écrivez actuellement.
Mark Amery

1
Génial. Je suis sur le point de mettre asynctoutes les méthodes dans mon application maintenant. Et c'est beaucoup. Cela ne peut-il pas simplement être la valeur par défaut?
ygoe

25

Si je lis bien votre question - le code qui veut l'appel synchrone à une méthode asynchrone s'exécute sur un thread de répartiteur suspendu. Et vous voulez réellement bloquer de façon synchrone ce thread jusqu'à ce que la méthode asynchrone soit terminée.

Les méthodes asynchrones en C # 5 sont Taskoptimisées en découpant efficacement la méthode en morceaux sous le capot et en renvoyant une méthode qui peut suivre l'achèvement global de l'ensemble du shabang. Cependant, la façon dont les méthodes hachées s'exécutent peut dépendre du type de l'expression transmise à l' awaitopérateur.

La plupart du temps, vous utiliserez awaitune expression de type Task. L'implémentation du awaitmodèle par Task est "intelligente" en ce sens qu'elle diffère de la SynchronizationContext, ce qui provoque essentiellement les événements suivants:

  1. Si le thread entrant dans se awaittrouve sur un thread de boucle de message Dispatcher ou WinForms, il garantit que les segments de la méthode async se produisent dans le cadre du traitement de la file d'attente de messages.
  2. Si le thread entrant dans awaitest sur un thread de pool de threads, les segments restants de la méthode async se produisent n'importe où sur le pool de threads.

C'est pourquoi vous rencontrez probablement des problèmes - l'implémentation de la méthode async essaie d'exécuter le reste sur le Dispatcher - même si elle est suspendue.

.... sauvegarde! ....

Je dois poser la question, pourquoi essayez-vous de bloquer de manière synchrone sur une méthode asynchrone? Cela irait à l'encontre de l'objectif pour lequel la méthode voulait être appelée de manière asynchrone. En général, lorsque vous commencez à utiliser awaitune méthode Dispatcher ou UI, vous souhaiterez activer la synchronisation asynchrone de l'ensemble de votre flux d'interface utilisateur. Par exemple, si votre pile d'appels était quelque chose comme ceci:

  1. [Haut] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()- WPFou WinFormsCode
  6. [Message Loop] - WPFou WinFormsMessage Loop

Ensuite, une fois que le code a été transformé pour utiliser async, vous vous retrouverez généralement avec

  1. [Haut] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()- WPFou WinFormsCode
  6. [Message Loop] - WPFou WinFormsMessage Loop

Répondre réellement

La classe AsyncHelpers ci-dessus fonctionne réellement car elle se comporte comme une boucle de messages imbriquée, mais elle installe sa propre mécanique parallèle sur le Dispatcher plutôt que d'essayer de s'exécuter sur le Dispatcher lui-même. C'est une solution de contournement pour votre problème.

Une autre solution consiste à exécuter votre méthode asynchrone sur un thread threadpool, puis à attendre qu'elle se termine. Il est facile de le faire - vous pouvez le faire avec l'extrait de code suivant:

var customerList = TaskEx.RunEx(GetCustomers).Result;

L'API finale sera Task.Run (...), mais avec le CTP, vous aurez besoin des suffixes Ex ( explication ici ).


+1 pour l'explication détaillée, mais TaskEx.RunEx(GetCustomers).Resultbloque l'application lorsqu'elle est exécutée sur un thread de répartiteur suspendu. En outre, la méthode GetCustomers () est normalement exécutée en mode asynchrone, mais dans une situation, elle doit s'exécuter de manière synchrone, donc je cherchais un moyen de le faire sans créer de version de synchronisation de la méthode.
Rachel

+1 pour "pourquoi essayez-vous de bloquer de manière synchrone une méthode asynchrone?" Il y a toujours moyen d'utiliser correctement les asyncméthodes; les boucles imbriquées doivent certainement être évitées.
Stephen Cleary

24

Cela fonctionne bien pour moi

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}

Vous devez également utiliser la méthode Task.Unwrap , car votre instruction Task.Wait provoque l'attente de la tâche externe (créée par Task.Run ), pas de la tâche d' attente interne t passée en tant que paramètre de la méthode d'extension. Votre méthode Task.Run renvoie non pas la tâche <T>, mais la tâche <Task <T>>. Dans certains scénarios simples, votre solution peut fonctionner en raison des optimisations de TaskScheduler, par exemple en utilisant la méthode TryExecuteTaskInline pour exécuter des tâches dans le thread actuel pendant l' opération d' attente . Veuillez lire mon commentaire sur cette réponse.
sgnsajgon

1
Ce n'est pas correct. Le Task.Run renverra la tâche <T>. Voir cette surcharge msdn.microsoft.com/en-us/library/hh194918(v=vs.110).aspx
Clement

Comment est-ce censé être utilisé? Cette impasse dans WPF:MyAsyncMethod().RunTaskSynchronously();
ygoe

18

La manière la plus simple que j'ai trouvée pour exécuter la tâche de manière synchrone et sans bloquer le thread d'interface utilisateur est d'utiliser RunSynchronously () comme:

Task t = new Task(() => 
{ 
   //.... YOUR CODE ....
});
t.RunSynchronously();

Dans mon cas, j'ai un événement qui se déclenche lorsque quelque chose se produit. Je ne sais pas combien de fois cela se produira. Donc, j'utilise le code ci-dessus dans mon événement, donc chaque fois qu'il se déclenche, il crée une tâche. Les tâches sont exécutées de manière synchrone et cela fonctionne très bien pour moi. J'étais juste surpris qu'il m'ait fallu si longtemps pour découvrir cela, vu à quel point c'est simple. Habituellement, les recommandations sont beaucoup plus complexes et sujettes aux erreurs. C'était simple et propre.


1
Mais comment pourrions-nous utiliser cette méthode lorsque le code asynchrone renvoie quelque chose dont nous avons besoin?
S.Serpooshan

16

Je l'ai rencontré à plusieurs reprises, principalement lors de tests unitaires ou lors du développement d'un service Windows. Actuellement, j'utilise toujours cette fonctionnalité:

        var runSync = Task.Factory.StartNew(new Func<Task>(async () =>
        {
            Trace.WriteLine("Task runSync Start");
            await TaskEx.Delay(2000); // Simulates a method that returns a task and
                                      // inside it is possible that there
                                      // async keywords or anothers tasks
            Trace.WriteLine("Task runSync Completed");
        })).Unwrap();
        Trace.WriteLine("Before runSync Wait");
        runSync.Wait();
        Trace.WriteLine("After runSync Waited");

C'est simple, facile et je n'ai eu aucun problème.


C'est le seul qui ne m'ait pas bloqué.
AndreFeijo

15

J'ai trouvé ce code dans le composant Microsoft.AspNet.Identity.Core, et cela fonctionne.

private static readonly TaskFactory _myTaskFactory = new 
     TaskFactory(CancellationToken.None, TaskCreationOptions.None, 
     TaskContinuationOptions.None, TaskScheduler.Default);

// Microsoft.AspNet.Identity.AsyncHelper
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
    CultureInfo cultureUi = CultureInfo.CurrentUICulture;
    CultureInfo culture = CultureInfo.CurrentCulture;
    return AsyncHelper._myTaskFactory.StartNew<Task<TResult>>(delegate
    {
        Thread.CurrentThread.CurrentCulture = culture;
        Thread.CurrentThread.CurrentUICulture = cultureUi;
        return func();
    }).Unwrap<TResult>().GetAwaiter().GetResult();
}


13

Juste une petite note - cette approche:

Task<Customer> task = GetCustomers();
task.Wait()

fonctionne pour WinRT.

Laisse-moi expliquer:

private void TestMethod()
{
    Task<Customer> task = GetCustomers(); // call async method as sync and get task as result
    task.Wait(); // wait executing the method
    var customer = task.Result; // get's result.
    Debug.WriteLine(customer.Name); //print customer name
}
public class Customer
{
    public Customer()
    {
        new ManualResetEvent(false).WaitOne(TimeSpan.FromSeconds(5));//wait 5 second (long term operation)
    }
    public string Name { get; set; }
}
private Task<Customer> GetCustomers()
{
    return Task.Run(() => new Customer
    {
        Name = "MyName"
    });
}

De plus, cette approche ne fonctionne que pour les solutions du Windows Store!

Remarque: Cette méthode n'est pas sécurisée pour les threads si vous appelez votre méthode à l'intérieur d'une autre méthode asynchrone (selon les commentaires de @Servy)


J'ai expliqué cette solution, consultez la section EDIT.
RredCat

2
Cela peut très facilement entraîner des blocages lorsqu'il est appelé dans des situations asynchrones.
Servy

@Servy a du sens. Donc, comme je reçois correctement, l'attente (timeOut) peut aider, non?
RredCat

1
Ensuite, vous devez vous soucier d'avoir le délai d'attente atteint lorsque l'opération n'est pas réellement effectuée, ce qui est très mauvais, ainsi que le temps passé à attendre le délai dans les cas où il se bloque (et dans ce cas, vous continuez toujours quand ce n'est pas fait). Donc non, cela ne résout pas le problème.
Servy

@Servy On dirait que je dois implémenter CancellationTokenma solution.
RredCat

10

Dans votre code, votre première attente pour l'exécution de la tâche, mais vous ne l'avez pas démarrée, elle attend donc indéfiniment. Essaye ça:

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Éditer:

Vous dites que vous obtenez une exception. Veuillez poster plus de détails, y compris la trace de la pile.
Mono contient le cas de test suivant:

[Test]
public void ExecuteSynchronouslyTest ()
{
        var val = 0;
        Task t = new Task (() => { Thread.Sleep (100); val = 1; });
        t.RunSynchronously ();

        Assert.AreEqual (1, val);
}

Vérifiez si cela fonctionne pour vous. Si ce n'est pas le cas, bien que très peu probable, vous pourriez avoir une construction étrange d'Async CTP. Si cela fonctionne, vous souhaiterez peut-être examiner ce que le compilateur génère exactement et en quoi l' Taskinstanciation est différente de cet exemple.

Édition n ° 2:

J'ai vérifié avec Reflector que l'exception que vous avez décrite se produit quand m_actionest null. C'est un peu étrange, mais je ne suis pas un expert en CTP Async. Comme je l'ai dit, vous devez décompiler votre code et voir comment Taskest instancié exactement comment cela se m_actionfait null.


PS Quel est le problème avec les downvotes occasionnels? Envie d'élaborer?


J'ai ajusté ma question pour rendre le code que j'avais essayé un peu plus clair. RunSynchronously renvoie une erreur de RunSynchronously may not be called on a task unbound to a delegate. Google n'est d'aucune aide puisque tous les résultats pour cela sont en chinois ...
Rachel

Je pense que la différence est que je ne crée pas la tâche et que j'essaye de l'exécuter. Au lieu de cela, la tâche est créée par la méthode async lorsque le awaitmot clé est utilisé. L'exception publiée dans mon commentaire précédent est l'exception que je reçois, bien que ce soit l'un des rares pour lesquels je ne puisse pas rechercher de cause ou de résolution.
Rachel

1
asyncet les asyncmots clés ne sont rien d'autre que du sucre de syntaxe. Compilateur génère du code pour créer Task<Customer>dans GetCustomers()c'est là que je regarderais d' abord. En ce qui concerne l'exception, vous avez uniquement publié un message d'exception, ce qui est inutile sans type d'exception et trace de pile. Appelez la ToString()méthode d'exception et publiez la sortie dans la question.
Dan Abramov

@gaearon: J'ai publié les détails de l'exception et la trace de la pile dans ma question d'origine.
Rachel

2
@gaearon Je pense que vous aviez eu des downvotes parce que votre message n'est pas applicable à la question. La discussion porte sur les méthodes d'attente asynchrone, pas sur les méthodes simples de retour de tâche. De plus, à mon avis, le mécanisme d'attente asynchrone est un sucre de syntaxe, mais pas si trivial - il y a la poursuite, la capture de contexte, la reprise du contexte local, la gestion améliorée des exceptions locales, et plus encore. Ensuite, vous ne devez pas appeler la méthode RunSynchronously sur le résultat de la méthode async, car par définition, la méthode asynchrone doit renvoyer la tâche qui est actuellement au moins planifiée, et plus d'une fois est en cours d'exécution.
sgnsajgon

9

Testé dans .Net 4.6. Il peut également éviter l'impasse.

Pour le retour de la méthode async Task.

Task DoSomeWork();
Task.Run(async () => await DoSomeWork()).Wait();

Pour le retour de la méthode async Task<T>

Task<T> GetSomeValue();
var result = Task.Run(() => GetSomeValue()).Result;

Modifier :

Si l'appelant est en cours d'exécution dans le thread de pool de threads (ou l'appelant est également dans une tâche), il peut toujours provoquer un blocage dans certaines situations.


1
Ma réponse après presque 8 ans :) Le deuxième exemple - produira un blocage dans tous les contextes planifiés qui sont principalement utilisés (application console / noyau .NET / application de bureau / ...). ici, vous avez plus d'aperçu de quoi je parle maintenant: medium.com/rubrikkgroup/…
W92

Resultest parfait pour le travail si vous voulez un appel synchrone, et carrément dangereux sinon. Il n'y a rien dans le nom Resultou dans l'intellisense Resultqui indique qu'il s'agit d'un appel bloquant. Il devrait vraiment être renommé.
Zodman

5

utiliser le code ci-dessous

Task.WaitAll(Task.Run(async () => await service.myAsyncMethod()));

4

Pourquoi ne pas créer un appel comme:

Service.GetCustomers();

ce n'est pas asynchrone.


4
Ce sera ce que je ferai si je ne peux pas faire fonctionner cela ... créer une version Sync en plus d'une version Async
Rachel

3

Cette réponse est conçue pour tous ceux qui utilisent WPF pour .NET 4.5.

Si vous essayez de l'exécuter Task.Run()sur le thread GUI, puis task.Wait()se bloque indéfiniment, si vous n'avez pas le asyncmot - clé dans votre définition de fonction.

Cette méthode d'extension résout le problème en vérifiant si nous sommes sur le thread GUI, et si c'est le cas, en exécutant la tâche sur le thread de répartiteur WPF.

Cette classe peut servir de lien entre le monde asynchrone / attente et le monde non asynchrone / attente, dans les situations où cela est inévitable, comme les propriétés MVVM ou les dépendances avec d'autres API qui n'utilisent pas async / wait.

/// <summary>
///     Intent: runs an async/await task synchronously. Designed for use with WPF.
///     Normally, under WPF, if task.Wait() is executed on the GUI thread without async
///     in the function signature, it will hang with a threading deadlock, this class 
///     solves that problem.
/// </summary>
public static class TaskHelper
{
    public static void MyRunTaskSynchronously(this Task task)
    {
        if (MyIfWpfDispatcherThread)
        {
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E99213. Task did not run to completion.");
            }
        }
        else
        {
            task.Wait();
            if (task.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E33213. Task did not run to completion.");
            }
        }
    }

    public static T MyRunTaskSynchronously<T>(this Task<T> task)
    {       
        if (MyIfWpfDispatcherThread)
        {
            T res = default(T);
            var result = Dispatcher.CurrentDispatcher.InvokeAsync(async () => { res = await task; });
            result.Wait();
            if (result.Status != DispatcherOperationStatus.Completed)
            {
                throw new Exception("Error E89213. Task did not run to completion.");
            }
            return res;
        }
        else
        {
            T res = default(T);
            var result = Task.Run(async () => res = await task);
            result.Wait();
            if (result.Status != TaskStatus.RanToCompletion)
            {
                throw new Exception("Error E12823. Task did not run to completion.");
            }
            return res;
        }
    }

    /// <summary>
    ///     If the task is running on the WPF dispatcher thread.
    /// </summary>
    public static bool MyIfWpfDispatcherThread
    {
        get
        {
            return Application.Current.Dispatcher.CheckAccess();
        }
    }
}

3

Un simple appel .Result;ou .Wait()un risque de blocage comme beaucoup l'ont dit dans les commentaires. Comme la plupart d'entre nous aiment les oneliners, vous pouvez les utiliser pour.Net 4.5<

Acquisition d'une valeur via une méthode asynchrone:

var result = Task.Run(() => asyncGetValue()).Result;

Appel synchrone d'une méthode asynchrone

Task.Run(() => asyncMethod()).Wait();

Aucun problème de blocage ne se produira en raison de l'utilisation de Task.Run.

La source:

https://stackoverflow.com/a/32429753/3850405


1

Je pense que la méthode d'assistance suivante pourrait également résoudre le problème.

private TResult InvokeAsyncFuncSynchronously<TResult>(Func< Task<TResult>> func)
    {
        TResult result = default(TResult);
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try
            {
                result = await func();
            }
            catch (Exception exc)
            {
                mErrorLogger.LogError(exc.ToString());
            }
            finally
            {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

Peut être utilisé de la manière suivante:

InvokeAsyncFuncSynchronously(Service.GetCustomersAsync);

1
Veuillez expliquer le vote
donttellya

2
... Je suis toujours très curieux de savoir pourquoi cette réponse a été rejetée.
donttellya

Ce n'est pas un vrai "synchrone". Vous créez deux threads et attendez les premiers résultats de l'autre.
tmt

et toutes choses mises à part, c'est une très mauvaise idée.
Dan Pantry

1
Je viens d'écrire presque le code identique (ligne par ligne la même) mais en utilisant à la place SemaphoreSlim au lieu de l'événement de réinitialisation automatique. J'aimerais avoir vu ça plus tôt. Je trouve cette approche pour éviter les blocages et garder votre code asynchrone en cours d'exécution de la même manière que dans les vrais scénarios asynchrones. Je ne sais pas vraiment pourquoi c'est une mauvaise idée. Semble beaucoup plus propre que les autres approches que j'ai vues ci-dessus.
tmrog

0

Cela fonctionne pour moi

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    public static class AsyncHelper
    {
        private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None, TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);

        public static void RunSync(Func<Task> func)
        {
            _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }

        public static TResult RunSync<TResult>(Func<Task<TResult>> func)
        {
            return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
        }
    }

    class SomeClass
    {
        public async Task<object> LoginAsync(object loginInfo)
        {
            return await Task.FromResult(0);
        }
        public object Login(object loginInfo)
        {
            return AsyncHelper.RunSync(() => LoginAsync(loginInfo));
            //return this.LoginAsync(loginInfo).Result.Content;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var someClass = new SomeClass();

            Console.WriteLine(someClass.Login(1));
            Console.ReadLine();
        }
    }
}

-1

J'ai trouvé que SpinWait fonctionne assez bien pour cela.

var task = Task.Run(()=>DoSomethingAsyncronous());

if(!SpinWait.SpinUntil(()=>task.IsComplete, TimeSpan.FromSeconds(30)))
{//Task didn't complete within 30 seconds, fail...
   return false;
}

return true;

L'approche ci-dessus n'a pas besoin d'utiliser .Result ou .Wait (). Il vous permet également de spécifier un délai d'expiration afin que vous ne soyez pas bloqué pour toujours au cas où la tâche ne se terminerait jamais.


1
Le downvote suggère que quelqu'un n'aime pas cette méthode. Y a-t-il quelqu'un qui peut commenter l'inconvénient?
Grax32

En l'absence du vote négatif disant POURQUOI le vote négatif a été donné, quelqu'un peut-il le voter? :-)
Curtis

1
Ceci est une interrogation (rotation), le délégué prendra le fil du pool jusqu'à 1000 fois par seconde. Il peut ne pas retourner le contrôle immédiatement après la fin de la tâche (jusqu'à 10 + erreur ms ). Si le délai est terminé, la tâche continue de s'exécuter, ce qui rend le délai pratiquement inutile.
Sinatr

En fait, j'utilise cela partout dans mon code et lorsque la condition est remplie, SpinWaitSpinUntil () se ferme immédiatement. Ainsi, selon la première éventualité, «condition remplie» ou délai d'expiration, la tâche se termine. Il ne continue pas de fonctionner.
Curtis

-3

Sur wp8:

Emballe-le:

Task GetCustomersSynchronously()
{
    Task t = new Task(async () =>
    {
        myCustomers = await GetCustomers();
    }
    t.RunSynchronously();
}

Appeler:

GetCustomersSynchronously();

3
Non, cela ne fonctionnera pas, car la tâche n'attend pas le délégué du constructeur (c'est un délégué et non une tâche ..)
Rico Suter

-4
    private int GetSync()
    {
        try
        {
            ManualResetEvent mre = new ManualResetEvent(false);
            int result = null;

            Parallel.Invoke(async () =>
            {
                result = await SomeCalcAsync(5+5);
                mre.Set();
            });

            mre.WaitOne();
            return result;
        }
        catch (Exception)
        {
            return null;
        }
    }

-5

Ou vous pouvez simplement aller avec:

customerList = Task.Run<List<Customer>>(() => { return GetCustomers(); }).Result;

Pour cela, assurez-vous de référencer l'assembly d'extension:

System.Net.Http.Formatting

-9

Essayez le code suivant, cela fonctionne pour moi:

public async void TaskSearchOnTaskList (SearchModel searchModel)
{
    try
    {
        List<EventsTasksModel> taskSearchList = await Task.Run(
            () => MakeasyncSearchRequest(searchModel),
            cancelTaskSearchToken.Token);

        if (cancelTaskSearchToken.IsCancellationRequested
                || string.IsNullOrEmpty(rid_agendaview_search_eventsbox.Text))
        {
            return;
        }

        if (taskSearchList == null || taskSearchList[0].result == Constants.ZERO)
        {
            RunOnUiThread(() => {
                textViewNoMembers.Visibility = ViewStates.Visible;                  
                taskListView.Visibility = ViewStates.Gone;
            });

            taskSearchRecureList = null;

            return;
        }
        else
        {
            taskSearchRecureList = TaskFooterServiceLayer
                                       .GetRecurringEvent(taskSearchList);

            this.SetOnAdapter(taskSearchRecureList);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine("ActivityTaskFooter -> TaskSearchOnTaskList:" + ex.Message);
    }
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.