2016-07-22 5 views
0

Ich habe eine einfache Demo-Klasse zusammen, die wie folgt aussieht:Eine Referenz in einer Aufgabe pflegen?

public class HelloWorld 
    { 
     public string Name { get; set; } 
    } 

    public Main() 
    { 
     var h = new HelloWorld() { Name = "A" }; 
     Task.Factory.StartNew(() => { Console.WriteLine(h.Name); }); 
     h = new HelloWorld() { Name = "B" }; 
    } 

Der folgende Code druckt:

Welche vollkommen logisch ist, aber nicht, was

B I will (ich möchte A drucken). Ich würde erwarten, in der Lage sein, StartNew() mit einem Argument aufrufen, die den ersten Verweis auf h innerhalb der Delegat beibehalten würde, aber ich kann diese Option nicht sehen.

Fehle ich etwas?

Edit: Ich sehe, dass ich zwar in einer Art object

Task.Factory.StartNew(new Action<object>((obj) => { Console.WriteLine((obj as Hello).Name); }),h); 

Gezwungen, zu passieren scheint ein wenig .NET 1.1/pre-Generika mir verwenden können, so dass für eine bessere Option zu hoffen.

+0

Nebenbei bemerkt, Sie sollten 'StartNew' nicht verwenden. Verwenden Sie stattdessen "Task.Run". –

Antwort

1

Was Sie gefunden haben, ist eine Schließung genannt und es ist nicht einzigartig für Aufgaben. Jedes Mal, wenn Sie eine Variable in einem Lambda verwenden, wird es vom Compiler in einer speziellen Klasse erfasst, die es nur für diesen Zweck erstellt. Der Compiler erzeugt ungefähr so ​​etwas wie:

public void Main() 
{ 
    var closure = new Main_Closure(); 
    closure.h = new HelloWorld() { Name = "A" }; 
    Task.Factory.StartNew(closure.M1); 
    closure.h = new HelloWorld() { Name = "B" }; 
} 

class Main_Closure 
{ 
    public HelloWorld h; 

    public void M1() 
    { 
     Console.WriteLine(h.Name); 
    } 
} 

Und da closure.h konnte wieder zugewiesen werden, bevor die Task gestartet wird, erhalten Sie das Ergebnis Sie sehen.

In diesem Fall können Sie einfach eine andere Variable verwenden, um Ihr neues Objekt zu speichern. Oder verwende eine andere Variable kurz vor dem Aufrufen des Lambda, z.

var h1 = h; 
Task.Factory.StartNew(() => { Console.WriteLine(h1.Name); }); 
-2

Was Sie erleben, ist ein sehr leistungsfähiger Mechanismus namens Schließung. Es ist sehr nützlich in einer Reihe von Umständen. Erfahren Sie mehr über die Funktionsweise von Verschlüssen: http://csharpindepth.com/Articles/Chapter5/Closures.aspx

Das Problem in Ihrem Fall ist, dass h sich ändert, bevor die Aufgabe ausgeführt werden konnte. Beachten Sie, dass dies nur Glück ist. Manchmal wird die Aufgabe zuerst ausgeführt, andere möglicherweise nicht.

Eine Sache, die Sie möglicherweise in Ihrem Fall tun können, um dies zu beheben, ist einfach erwarten die Aufgabe.

Wenn Ihr Code auf einer Hauptmethode ist, können Sie dies erreichen, indem einfach .Wait Zugabe() am Ende der Linie:

var h = new HelloWorld() { Name = "A" }; 
Task.Factory.StartNew(() => { Console.WriteLine(h.Name); }).Wait(); 
h = new HelloWorld() { Name = "B" }; 

Wenn Sie auf jede andere Methode sind Sie einfach die Aufgabe warten kann mit dem await Schlüsselwort und machen das Verfahren async:

public async Task MyMethod() 
{ 
    var h = new HelloWorld() { Name = "A" }; 
    await Task.Factory.StartNew(() => { Console.WriteLine(h.Name); }); 
    h = new HelloWorld() { Name = "B" }; 
} 

auch bedenken, dass die Verwendung von Task.Factory.StartNew in den meisten Fällen nicht die beste Option ist. Versuchen Sie stattdessen die Verwendung von Task.Run zu bevorzugen.

+0

Wie ich @rinu kommentiert, ändert dies vollständig die Semantik der Methode. –

+0

@EliArbel hat Recht. Stellen Sie sich vor, wenn ich die Deklaration von "h" in den Geltungsbereich des Methodenkörpers ändere - Sie könnten immer noch nicht sicher sein, dass bis zum Abschluss der Schließung die gleiche Referenz unabhängig von "erwarten" oder nicht haben würde. – maxp

+0

@EliArbel, natürlich tut es, .Wait() wird sperren, bis die Aufgabe abgeschlossen ist und wartet auf eine Fortsetzung. – Pablo

0

Wrap-Aufgabenerstellung in einer Methode, um das Schließen zu verhindern.

static Task DoAsync<T>(Action<T> action, T arg) 
{ 
    return Task.Run(() => action(arg)); 
} 

static void Main(string[] args) 
{ 
    Action<HelloWorld> hello = (HelloWorld h2) => { Console.WriteLine(h2.Name); }; 

    var h = new HelloWorld() { Name = "A" }; 
    Task task = DoAsync(hello, h); 
    var h = new HelloWorld() { Name = "B" }; 
} 
Verwandte Themen