Kullanıcı arabirimi iş parçacığında görev devamı


214

İlk görevin oluşturulduğu iş parçacığında bir görev devamının çalışması gerektiğini belirtmenin 'standart' bir yolu var mı?

Şu anda aşağıdaki kodu var - çalışıyor ama dağıtıcı takip ve ikinci bir eylem oluşturmak gereksiz ek yük gibi görünüyor.

dispatcher = Dispatcher.CurrentDispatcher;
Task task = Task.Factory.StartNew(() =>
{
    DoLongRunningWork();
});

Task UITask= task.ContinueWith(() =>
{
    dispatcher.Invoke(new Action(() =>
    {
        this.TextBlock1.Text = "Complete"; 
    }
});

Örneğinizde Control.Invoke(Action), yani. TextBlock1.Invokeyerinedispatcher.Invoke
Albay Panik

2
@ColonelPanic teşekkürler, ama winforms değil, (etiketli olarak) WPF kullanıyordum.
Greg Sansom

Yanıtlar:


352

Devamını şu numara ile arayın TaskScheduler.FromCurrentSynchronizationContext():

    Task UITask= task.ContinueWith(() =>
    {
     this.TextBlock1.Text = "Complete"; 
    }, TaskScheduler.FromCurrentSynchronizationContext());

Bu yalnızca geçerli yürütme içeriği UI iş parçacığındaysa uygundur.


39
Yalnızca geçerli yürütme içeriği UI iş parçacığındaysa geçerlidir. Bu kodu başka bir görevin içine koyarsanız, InvalidOperationException ( İstisnalar bölümüne bakın)
stukselbax

3
.NET 4.5'te Johan Larsson'un yanıtı, UI iş parçacığında görevin devam etmesi için standart bir yol olarak kullanılmalıdır. Sadece şunu yazın: Task.Run (DoLongRunningWork); this.TextBlock1.Text = "Tamamlandı"; Ayrıca bkz: blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Marcel W

1
Hayatımı kurtarmak için teşekkürler. Ben bekliyor / ContinueWith içinde ana iş parçacığı şeyler aramak için nasıl saatlerce harcamak. Herkes için Google Firebase SDK'yı Unity için nasıl kullanıyor ve aynı sorunları yaşıyorsa , bu çalışan bir yaklaşımdır.
CHaP

2
@MarcelW - awaitiyi bir kalıptır - ancak yalnızca bir asyncbağlamın içindeyseniz (bildirilen bir yöntem gibi async). Değilse, bu cevap gibi bir şey yapmak hala gereklidir.
ToolmakerSteve

33

Asenkron ile şunları yapabilirsiniz:

await Task.Run(() => do some stuff);
// continue doing stuff on the same context as before.
// while it is the default it is nice to be explicit about it with:
await Task.Run(() => do some stuff).ConfigureAwait(true);

Ancak:

await Task.Run(() => do some stuff).ConfigureAwait(false);
// continue doing stuff on the same thread as the task finished on.

2
falseSürümün altındaki yorum beni şaşırtıyor. Farklı bir iş parçacığında falsedevam edebileceğini düşündüm .
ToolmakerSteve

1
@ToolmakerSteve Hangi iş parçacığını düşündüğünüze bağlıdır. Task.Run tarafından kullanılan işçi iş parçacığı mı yoksa arayan iş parçacığı mı? Unutmayın, "görevin bittiği aynı iş parçacığı" çalışan iş parçacığı anlamına gelir (iş parçacıkları arasında 'geçiş yapmaktan kaçınmak). Ayrıca, ConfigureAwait (true), denetimin aynı iş parçacığına , yalnızca aynı içeriğe döndüğünü garanti etmez (ayrım önemli olmayabilir).
Max Barraclough

@MaxBarraclough - Teşekkürler, hangi "aynı iş parçacığı" anlamına geliyordu yanlış okudum. benim için açıklığa kavuşturan ["bazı şeyler yap" görevini yerine getirmek için) ne iş parçacığı kullanarak performansı en üst düzeye çıkarmak açısından iş parçacıkları arasında geçiş yapmaktan kaçınmak .
ToolmakerSteve

1
Soru, bir asyncyöntemin içinde (belirtilmesi gereken) olmayı belirtmemektedir await. awaitMevcut olmadığında cevap nedir ?
ToolmakerSteve

22

Kullanıcı arayüzüne göndermeniz gereken bir dönüş değeriniz varsa, aşağıdaki gibi genel sürümü kullanabilirsiniz:

Bu benim durumumda bir MVVM ViewModel deniyor.

var updateManifest = Task<ShippingManifest>.Run(() =>
    {
        Thread.Sleep(5000);  // prove it's really working!

        // GenerateManifest calls service and returns 'ShippingManifest' object 
        return GenerateManifest();  
    })

    .ContinueWith(manifest =>
    {
        // MVVM property
        this.ShippingManifest = manifest.Result;

        // or if you are not using MVVM...
        // txtShippingManifest.Text = manifest.Result.ToString();    

        System.Diagnostics.Debug.WriteLine("UI manifest updated - " + DateTime.Now);

    }, TaskScheduler.FromCurrentSynchronizationContext());

GenerateManifest bir yazım hatası önce = tahmin ediyorum.
Sebastien

Evet - Şimdi gitti! Teşekkürler.
Simon_Weaver

11

Ben sadece bu sürümü eklemek istedim çünkü bu yararlı bir iş parçacığı ve bu çok basit bir uygulama olduğunu düşünüyorum. Çok iş parçacıklı uygulama varsa bu çeşitli tiplerde birden çok kez kullandım:

 Task.Factory.StartNew(() =>
      {
        DoLongRunningWork();
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
              { txt.Text = "Complete"; }));
      });

2
Bazı senaryolarda bu geçerli bir çözüm olduğu için aşağı oy değil; ancak, kabul edilen cevap çok daha iyidir. Teknoloji açısından agnostiktir ( TaskSchedulerBCL'nin bir parçasıdır Dispatcher, değildir) ve herhangi bir yangın ve unutma asenkron işlemi (örneğin BeginInvoke) hakkında endişelenmemesi nedeniyle karmaşık görev zincirlerini oluşturmak için kullanılabilir .
Kirill Shlenskiy

@Kirill biraz genişletebilirsiniz, çünkü bazı SO iş parçacıkları oybirliğiyle WinForms'un WPF'sini kullanıyorsa dağıtıcıyı doğru yöntem olarak ilan ettiler: Biri GUI güncellemesini eşzamansız (BeginInvoke kullanarak) veya eşzamanlı olarak (Çağır), genellikle eşzamansız olarak çağırır yalnızca GUI güncellemesi için bir arka plan iş parçacığını engellemek istemediğinden kullanılır. FromCurrentSynchronizationContext, devam görevini ana iş parçacığı ileti kuyruğuna dağıtım programıyla aynı şekilde koymuyor mu?
Dean

1
Doğru, ama OP kesinlikle WPF hakkında soruyor (ve bu şekilde etiketlenmiş) ve herhangi bir dağıtıcıya bir referans tutmak istemiyor (ve ben de herhangi bir senkronizasyon bağlamı varsayalım - sadece ana iş parçacığı alabilirsiniz ve bunu yapmak zorunda referansı bir yerde saklayın). Bu yüzden ben gönderdiğim çözümü seviyorum: hiçbiri gerektirmeyen yerleşik bir iş parçacığı güvenli statik başvuru var. Bunun WPF bağlamında son derece yararlı olduğunu düşünüyorum.
Dean

3
Sadece son yorumumu güçlendirmek istedim: Geliştirici sadece senkronizasyon bağlamını saklamakla kalmaz, aynı zamanda bunun sadece ana iş parçacığından elde edilebilir olduğunu bilmelidir; bu sorun onlarca SO sorusunda karışıklığa neden olmuştur: İnsanlar her zaman bunu işçi iş parçacığından almaya çalışırlar. Kodları bir işçi iş parçacığına taşındıysa, bu sorun nedeniyle başarısız olur. Bu nedenle, WPF yaygınlığı nedeniyle, bu popüler soruda bu kesinlikle açıklığa kavuşturulmalıdır.
Dean

1
... yine de, Dean'in kodun ana iş parçacığında olmaması durumunda senkronizasyon bağlamını takip etmesi gereken [kabul edilen cevap] hakkındaki gözlemini not etmek önemlidir ve bundan kaçınmak bu cevabın bir yararıdır.
ToolmakerSteve

1

Çünkü bir Task.Run çağrı içinde olduktan sonra ui iş parçacığı üzerinde şeyler yapmak için iyi bir yol arıyordu çünkü google üzerinden var - Aşağıdaki kodu kullanarak awaittekrar UI iş parçacığına geri almak için kullanabilirsiniz .

Umarım bu birine yardımcı olur.

public static class UI
{
    public static DispatcherAwaiter Thread => new DispatcherAwaiter();
}

public struct DispatcherAwaiter : INotifyCompletion
{
    public bool IsCompleted => Application.Current.Dispatcher.CheckAccess();

    public void OnCompleted(Action continuation) => Application.Current.Dispatcher.Invoke(continuation);

    public void GetResult() { }

    public DispatcherAwaiter GetAwaiter()
    {
        return this;
    }
}

Kullanımı:

... code which is executed on the background thread...
await UI.Thread;
... code which will be run in the application dispatcher (ui thread) ...

1
Çok zeki! Yine de oldukça kasıtsız. staticSınıf yapmayı öneririm UI.
Theodor Zoulias
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.