2016-05-30 5 views
2

Gegeben eine SynchronizationContext, die ich bereits habe (und ist im Grunde ein Fenster zu einem bestimmten Thread), wie erstelle ich Task s, die zu diesem Kontext gepostet werden?Wie erstelle ich Aufgaben, die an einen bestimmten SynchronizationContext gesendet werden?

Als Referenz ist hier eine sehr einfache Demonstration, wie die SynchronizationContext eingerichtet ist. Diese

public class SomeDispatcher : SynchronizationContext 
{ 
    SomeDispatcher() { 

     new Thread(() => { 

      SynchronizationContext.SetSynchronizationContext(this); 

      // Dispatching loop (among other things) 

     }).Start(); 
    } 

    override void Post(SendOrPostCallback d, object state) 
    { 
     // Add (d, state) to a dispatch queue; 
    } 
} 

funktioniert gut für async/ erwartet, die bereits im Rahmen ausgeführt wird.

Jetzt möchte ich in der Lage sein, Task s von einem externen Zusammenhang (z. B. von einem UI-Thread) zu senden, aber kann nicht scheinen, einen sauberen Weg zu finden, dies zu tun.

Ein Weg, dies zu tun ist mit TaskCompletionSource<>.

Task StartTask(Action action) 
{ 
    var tcs = new TaskCompletionSource<object>(); 
    SaidDispatcher.Post(state => { 
     try 
     { 
      action.Invoke(); 
      tcs.SetResult(null); 
     } 
     catch (Exception ex) 
     { 
      tcs.SetException(ex); 
     } 
    }); 
    return tcs.Task; 
}); 

aber dies ist das Rad und einen großen Schmerz neu zu erfinden Unterstützung Variationen wie StartNew(Func<TResult>), StartNew(Func<Task<TResult>>) usw.

A TaskFactory Schnittstelle zum SynchronizationContext wahrscheinlich ideal ist, aber ich kann nicht scheinen, eine zu instanziiert sauber:

TaskFactory CreateTaskFactory() 
{ 
    var original = SynchronizationContext.Current; 
    SynchronizationContext.SetSynchronizationContext(SomeDispatcher); // yuck! 
    try 
    { 
     return new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext()); 
    } 
    finally 
    { 
     SynchronizationContext.SetSynchronizationContext(original); 
    } 
} 

(dh auf temporären Schlauch Mit Synchronisationskontext des aktuellen Thread scheint hacky.)

+0

Was ist mit der Implementierung von benutzerdefinierten TaskScheduler durch Kopieren der Logik des vorhandenen SynchronizationContextTaskScheduler? Das Kopieren ist natürlich nicht erforderlich, die Logik selbst sieht ziemlich einfach aus. – Evk

+0

@Evk Es gibt ein paar "interne", die Sie nicht benutzen können, aber die Hauptidee ist immer noch da - Sie brauchen Ihren eigenen 'TaskScheduler'. – Luaan

+1

Warum möchten Sie eine Aufgabe im Synchronisationskontext posten? Die übliche Verwendung von Synchronisationskontexten beginnt im Synchronisationskontext. –

Antwort

4

Es scheint Standard SynchronizationContextTaskScheduler ist

  1. Interne
  2. funktioniert nur mit aktuellen Synchronisationskontext

Aber es ist Quellcode here verfügbar ist und wir sehen es relativ einfach ist, so können wir versuchen zu rollen aus unserem eigenen Scheduler, wie folgt:

public sealed class MySynchronizationContextTaskScheduler : TaskScheduler { 
    private readonly SynchronizationContext _synchronizationContext; 

    public MySynchronizationContextTaskScheduler(SynchronizationContext context) { 
     _synchronizationContext = context; 
    } 

    [SecurityCritical] 
    protected override void QueueTask(Task task) { 
     _synchronizationContext.Post(PostCallback, task); 
    } 

    [SecurityCritical] 
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { 
     if (SynchronizationContext.Current == _synchronizationContext) { 
      return TryExecuteTask(task); 
     } 
     else 
      return false; 
    } 

    [SecurityCritical] 
    protected override IEnumerable<Task> GetScheduledTasks() { 
     return null; 
    } 

    public override Int32 MaximumConcurrencyLevel 
    { 
     get { return 1; } 
    } 

    private void PostCallback(object obj) { 
     Task task = (Task) obj; 
     base.TryExecuteTask(task); 
    } 
} 

Dann Ihre CreateTaskFactory wird:

TaskFactory CreateTaskFactory() { 
    return new TaskFactory(new MySynchronizationContextTaskScheduler(SomeDispatcher)); 
} 

und erstellen Sie Aufgaben mit:

var factory = CreateTaskFactory(); 
var task = factory.StartNew(...); 
+0

Könnten Sie auch ein Beispiel hinzufügen, wie Sie das verwenden können, wenn man bedenkt, wie man Aufgaben hinzufügt und nicht wie man einen Aufgabenplaner schreibt? – Default

+0

@Default hinzugefügt. – Evk

+0

Das sieht gut aus! Ich frage mich, warum sie keinen 'TaskScheduler.FromSynchronizationContext (SynchronizationContext)' anbieten, und wenn es ein Designproblem gibt, das einen alten Synchronisationskontext zulässt, aber ich kann mir keinen vorstellen. (edit: deleted noise) – antak

2

Parallel Extensions Extras contains SynchronizationContextTaskScheduler die genau das tut, was Sie wollen.

Wenn Sie PEE nicht selbst kompilieren möchten, there is an unofficial NuGet package for it.

Beachten Sie, dass Sie das im Allgemeinen nicht tun sollten und die Tatsache, dass Sie darum bitten, könnte einen Fehler in Ihrem Design anzeigen.

+0

Gut zu wissen, dass dieser Anwendungsfall zumindest indirekt unterstützt wird. * Sie sollten dies normalerweise nicht tun *: Angenommen, ich habe einen sehr angepassten Task-Dispatcher (ein einzelner Thread mit einer Task-Aufrufschleife im Kern), dem ich (möglicherweise rückwirkend) * async */* awa * hinzufügen möchte Unterstützung. Der natürliche Verlauf scheint zu sein, implementieren Sie 'SynchronizationContext' ->' TaskScheduler' -> 'TaskFactory'. Gibt es einen besseren Weg? (Oder wollten Sie einen benutzerdefinierten Dispatcher erstellen, der möglicherweise ein Problem darstellt?) – antak

+0

@antak Warum brauchen Sie diesen 'SynchronizationContext'? Wäre ein benutzerdefinierter TaskScheduler nicht genug? – svick

+0

Ich war für einen Moment aufgeregt, da ich dachte, ich könnte 'TaskScheduler.Current' anstelle von' syncctx.Current' einstellen, um [get * await * s zu erhalten, um im selben Thread fortzufahren] (https://msdn.microsoft.com/ Magazin/gg598924.aspx). "TaskScheduler.Current" [scheint jedoch etwas ganz anderes zu sein] (http://stackoverflow.com/a/23072503/1036728) und ist nicht irgendein Thread-Local, das ich einstellen kann. Wenn alles, was der Dispatcher tut, durch den 'TaskScheduler' kommt, ist dies wahrscheinlich kein Problem, aber sobald er eine Routine außerhalb einer' Task' aufruft, beginnt * zu warten * s zu brechen. Gibt es etwas, das mir fehlt? – antak

Verwandte Themen