webentwicklung-frage-antwort-db.com.de

Zugriff auf UI-Steuerelemente in Task.Run mit async/await unter WinForms

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?

19
Wouter de Kort

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";
}
24
svick

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?

14

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.

8
Metro

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.

6
Persi

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/

0
Gerrie Schenck