Ich habe den folgenden Code in einer WinForms-Anwendung mit einer Schaltfläche und einem Label:
using System;
using System.IO;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
});
}
}
}
Dies ist eine vereinfachte Version der echten Anwendung, an der ich gerade arbeite. Ich hatte den Eindruck, dass ich durch Verwendung von async/await in meinem Task.Run
die label1.Text
-Eigenschaft festlegen konnte. Beim Ausführen dieses Codes wird jedoch die Fehlermeldung angezeigt, dass ich mich nicht im UI-Thread befinde und nicht auf das Steuerelement zugreifen kann.
Warum kann ich nicht auf die Etikettensteuerung zugreifen?
Wenn Sie Task.Run()
verwenden, sagen Sie, dass Sie nicht möchten, dass der Code im aktuellen Kontext ausgeführt wird.
Es ist jedoch nicht notwendig, Task.Run()
in Ihrem Code zu verwenden. Richtig geschriebene async
-Methoden blockieren den aktuellen Thread nicht, sodass Sie sie direkt vom UI-Thread aus verwenden können. Wenn Sie dies tun, stellt await
sicher, dass die Methode im UI-Thread wieder aufgenommen wird.
Das heißt, wenn Sie Ihren Code so schreiben, wird er funktionieren:
private async void button1_Click(object sender, EventArgs e)
{
await Run();
}
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
Versuche dies
private async Task Run()
{
await Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
label1.Text = "test";
}
Oder
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
Oder
private async Task Run()
{
var task = Task.Run(async () => {
await File.AppendText("temp.dat").WriteAsync("a");
});
var continuation = task.ContinueWith(antecedent=> label1.Text = "test",TaskScheduler.FromCurrentSynchronizationContext());
await task;//I think await here is redundant
}
async/await garantiert nicht, dass es im UI-Thread ausgeführt wird. await
erfasst die aktuelle SynchronizationContext
und setzt die Ausführung mit dem erfassten Kontext fort, sobald die Aufgabe abgeschlossen ist.
In Ihrem Fall haben Sie also eine verschachtelte await
, die sich innerhalb von Task.Run
befindet. Die zweite await
erfasst den Kontext, der nicht UiSynchronizationContext
sein wird, da er von WorkerThread
von ThreadPool
ausgeführt wird.
Beantwortet das Ihre Frage?
Versuche dies:
ersetzen
label1.Text = "test";
mit
SetLabel1Text("test");
und fügen Sie Ihrer Klasse Folgendes hinzu:
private void SetLabel1Text(string text)
{
if (InvokeRequired)
{
Invoke((Action<string>)SetLabel1Text, text);
return;
}
label1.Text = text;
}
InvokeRequired gibt true zurück, wenn Sie sich NICHT im UI-Thread befinden. Die Invoke () - Methode nimmt den Delegaten und die Parameter an, wechselt zum UI-Thread und ruft die Methode anschließend rekursiv auf. Sie kehren nach dem Aufruf von Invoke () zurück, da die Methode bereits rekursiv aufgerufen wurde, bevor Invoke () zurückkehrt. Wenn Sie sich beim Aufruf der Methode im UI-Thread befinden, ist InvokeRequired false und die Zuweisung wird direkt ausgeführt.
Warum verwenden Sie Task.Run? Starten Sie einen neuen Arbeitsthread (CPU-gebunden), und es verursacht Ihr Problem.
du solltest das wahrscheinlich nur tun:
private async Task Run()
{
await File.AppendText("temp.dat").WriteAsync("a");
label1.Text = "test";
}
erwarten Sie, dass Sie im selben Kontext weitermachen, außer wenn Sie .ConfigureAwait (false) verwenden.
Da es sich um einen anderen Thread handelt, sind Cross-Thread-Aufrufe nicht zulässig.
Sie müssen den "Kontext" an den Thread übergeben, den Sie starten. Ein Beispiel finden Sie hier: http://reedcopsey.com/2009/11/17/synchronizing-net-4-tasks-with-the-ui-thread/