Zu Hauptinhalt springen

 none
Asynchrone Hintergrundaufgaben RRS feed

  • Frage

  • Hallo liebe Community,

    ich finde keinen guten Ansatz für mein folgendes Vorhaben und stehe ein wenig auf dem Schlauch.

    In meiner Applikation sollen Tasks im Hintergrund ausgeführt werden, in denen über einen Webservice diverse Daten, in diesem Fall ein Bild, zum Server gesendet werden. Es sollen diverse Aktionen asynchron zum Server geschickt werden. Sofern keine Netzwerkverbindung vorhanden ist oder ein Fehler auftritt, soll nach einigen Minuten erneut versucht werden diese Aufgabe auszuführen. Diese Aufgaben sollen über ein Binding auf der Oberfläche sichtbar gemacht werden, ggf. in Form einer Liste und mit Prozess-Status (z.B. Retry 1).

    Mein erster Ansatz war folgender:

    Die Methode Upload() gibt ein IAsyncOperationWithProcess -Objekt zurück und speichert diesen Prozess in einer ObservableCollection. Diese binde ich in einer Liste. 

    Weiterer Ansatz:

    Weiterhin habe ich mir auch die Möglichkeit einer Hintergrundaufgabe über die Schnittstelle IBackgroundTask angeschaut. Dies erschien mir aber auch nicht der richtige Ansatz. 

    Ich bitte um Entschuldigung, wenn ich hier einen Beitrag öffne, den es vielleicht schon gibt. Ich bin jedoch schon sehr viel durchgegangen und finde noch nicht die perfekte Lösung für mein Vorhaben.

    Hat jemand einen Tipp oder Ansatz parat?

    Freitag, 21. Juni 2019 12:26

Antworten

  • Hi Marc,
    nachfolgend mal eine ganz einfache Demo (ohne Kapselung in separate Klasse). Klicke auf den Button und es wird das Warten auf das Ergebnis simuliert. Verarbeitet wird in einem separaten thread mittels Task.Run. Die Arbeitsmethode gibt einen string zurück. Bei komplexeren Daten kannst Du als Rückgabe ein eigenes Objekt nutzen, in welchem alle Member threadsicher sein sollten.

    XAML:

    <Page
        x:Class="App1.Page09"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel>
        <TextBlock Name="TEMP_CURRENT_TextBlock" Text="{x:Bind ViewModel.WeatherData_current_Temperature, Mode=OneWay}" />
        <TextBlock Name="TEMP_FORECAST_TextBlock" Text="{x:Bind ViewModel.WeatherData_forecast_Temperature, Mode=OneWay}" />
        <Button Content="Laden" Click="Button_Click" />
      </StackPanel>
    </Page>
    

    Dazu der Code:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    
    namespace App1
    {
      public sealed partial class Page09 : Page
      {
        public Page09()
        {
          this.InitializeComponent();
        }
        public Page09VM ViewModel { get; set; } = new Page09VM();
    
        private void Button_Click(object sender, RoutedEventArgs e) => ViewModel.LoadData();
      }
      public class Page09VM : INotifyPropertyChanged
      {
        string _weatherData_current_Temperature;
        public string WeatherData_current_Temperature
        {
          get => _weatherData_current_Temperature;
          set { _weatherData_current_Temperature = value; OnPropertyChanged(); }
        }
    
        string _weatherData_forecast_Temperature;
        public string WeatherData_forecast_Temperature
        {
          get => _weatherData_forecast_Temperature;
          set { _weatherData_forecast_Temperature = value; OnPropertyChanged(); }
        }
    
        internal async void LoadData()
        {
          WeatherData_current_Temperature = await WeatherUpdate("Anzeige 1");
          WeatherData_forecast_Temperature = await WeatherUpdate("Anzeige 2");
        }
    
        internal async Task<string> WeatherUpdate(string par)
        {
          return await Task<string>.Run(async () =>
          {
            await Task.Delay(2000); // Arbeitszeit simulieren
            return par;
          });
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Samstag, 22. Juni 2019 05:13
  • Hallo Marc,

    der IBackgroundTask ist dann sinnvoll wenn auch nach Beendigung der App etwas getan werden muss. Z.B. upload oder download von Dateien. Auch kann man den BackgroundTask so konfigurieren das er startet wenn sich der Zustand eine Netzwerkverbindung ändert (Internet).

    Ich persönlich mache es meist so das die App mit dem BackgroundTask nur über eine SQLite Datenbank kommuniziert.

    App stellt Aufgaben rein und der BackgroundTask arbeitet sie ab 


    Gruß Thomas
    13 Millionen Schweine landen jährlich im Müll
    Dev Apps von mir: UWP Segoe MDL2 Assets, UI Strings

    Samstag, 22. Juni 2019 17:30
  • Wie Du nun rausgefunden hast wird der Task als Windows Runtime Komponente erstellt, Resultat ist eine Winmd. Diese wird von Windows ausgeführt und nicht deiner App. Man kann mit diesem Task nicht so kommunizieren wie mit irgendeiner Klasse innerhalb der App. Man sollte den Task als eigenständiges Program ansehen. Kommunikationsmöglichkeiten sind in der Doku beschrieben.

    Wie ich oben schon geschrieben habe, kommuniziere ich mit den Task nur über eine DB oder ein Textdatei (Json/XML)


    Gruß Thomas
    13 Millionen Schweine landen jährlich im Müll
    Dev Apps von mir: UWP Segoe MDL2 Assets, UI Strings

    Donnerstag, 4. Juli 2019 13:58

Alle Antworten

  • Hi Marc,
    das Hauptproblem der asynchronen Arbeitsweise ist der thread-übergreifende Zugriff auf nicht thread-sichere Objekt, wie beispielsweise die Oberflächen-Elemente. Am besten ist es, wenn Du lang dauernde Aktivitäten, wie beispielsweise den Zugriff auf den externen Server, in einer separaten Klasse kapselst und alle Member dieser Klasse, die von der Oberfläche genetzt werden sollen, thread-sicher gestaltest. Dazu gehören z.B. auch die Instanziierung von Objekten, die diese Klasse liefern kann, das Auslösen von Ereignissen usw.

    In welcher Programmiersprache arbeitest Du? Vielleicht kann ich ein Beispiel beisteuern.


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Freitag, 21. Juni 2019 13:24
  • Hallo Peter, ja das wäre lieb. Die App entwickele ich in C#. Ich habe jetzt auch einen Ansatz der zumindest schon einmal funktioniert. Ich bin mir jedoch nicht sicher, ob das eine elegante Lösung ist. Wenn ich am Rechner sitze kann ich vielleicht mal ein Codeschnippsel einstellen. Würde mich natürlich auch über einen anderen Ansatz freuen :-) Liebe Grüße Marc
    Freitag, 21. Juni 2019 20:46
  • Hi Marc,
    nachfolgend mal eine ganz einfache Demo (ohne Kapselung in separate Klasse). Klicke auf den Button und es wird das Warten auf das Ergebnis simuliert. Verarbeitet wird in einem separaten thread mittels Task.Run. Die Arbeitsmethode gibt einen string zurück. Bei komplexeren Daten kannst Du als Rückgabe ein eigenes Objekt nutzen, in welchem alle Member threadsicher sein sollten.

    XAML:

    <Page
        x:Class="App1.Page09"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
      <StackPanel>
        <TextBlock Name="TEMP_CURRENT_TextBlock" Text="{x:Bind ViewModel.WeatherData_current_Temperature, Mode=OneWay}" />
        <TextBlock Name="TEMP_FORECAST_TextBlock" Text="{x:Bind ViewModel.WeatherData_forecast_Temperature, Mode=OneWay}" />
        <Button Content="Laden" Click="Button_Click" />
      </StackPanel>
    </Page>
    

    Dazu der Code:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading.Tasks;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    
    namespace App1
    {
      public sealed partial class Page09 : Page
      {
        public Page09()
        {
          this.InitializeComponent();
        }
        public Page09VM ViewModel { get; set; } = new Page09VM();
    
        private void Button_Click(object sender, RoutedEventArgs e) => ViewModel.LoadData();
      }
      public class Page09VM : INotifyPropertyChanged
      {
        string _weatherData_current_Temperature;
        public string WeatherData_current_Temperature
        {
          get => _weatherData_current_Temperature;
          set { _weatherData_current_Temperature = value; OnPropertyChanged(); }
        }
    
        string _weatherData_forecast_Temperature;
        public string WeatherData_forecast_Temperature
        {
          get => _weatherData_forecast_Temperature;
          set { _weatherData_forecast_Temperature = value; OnPropertyChanged(); }
        }
    
        internal async void LoadData()
        {
          WeatherData_current_Temperature = await WeatherUpdate("Anzeige 1");
          WeatherData_forecast_Temperature = await WeatherUpdate("Anzeige 2");
        }
    
        internal async Task<string> WeatherUpdate(string par)
        {
          return await Task<string>.Run(async () =>
          {
            await Task.Delay(2000); // Arbeitszeit simulieren
            return par;
          });
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Samstag, 22. Juni 2019 05:13
  • Hallo Marc,

    der IBackgroundTask ist dann sinnvoll wenn auch nach Beendigung der App etwas getan werden muss. Z.B. upload oder download von Dateien. Auch kann man den BackgroundTask so konfigurieren das er startet wenn sich der Zustand eine Netzwerkverbindung ändert (Internet).

    Ich persönlich mache es meist so das die App mit dem BackgroundTask nur über eine SQLite Datenbank kommuniziert.

    App stellt Aufgaben rein und der BackgroundTask arbeitet sie ab 


    Gruß Thomas
    13 Millionen Schweine landen jährlich im Müll
    Dev Apps von mir: UWP Segoe MDL2 Assets, UI Strings

    Samstag, 22. Juni 2019 17:30
  • Hallo Thomas,

    solch eine Hintergrundaufgabe wäre in der Tat vielleicht sogar das richtige für meine Anforderungen. So erfolgen diese Upload-Tasks im Hintergrund und auch dann, wenn die Applikation einmal abgestürzt ist (sollte natürlich nicht passieren). 

    Registrieren konnte ich den Task bisher auch schon, jedoch hänge ich noch daran, dass der CompleteEventHandler auch ausgelöst wird. Werde ich sicher noch lösen.

    Weiterhin stelle ich mir aber auch die Frage, ob man hier denn auch Objekte an den Task übergeben kann und entsprechende Resultate zurück bekommt. In der Doku gibt es zumindest kein Beispiel dafür.

    Gruß Marc

    Donnerstag, 4. Juli 2019 07:25
  • Ich habe jetzt herausgefunden, warum der Task nicht abschließt. Er scheint ihn nicht auszuführen... :D

    Im Ereignisprotokoll vom Windows bekomme ich folgende Meldung: 

    Fehler beim Aktivieren der Hintergrundaufgabe mit dem Einstiegspunkt "Threading.lib.BackgroundTask" und der Bezeichnung "Mein Hintergrundtask". Fehlercode: 0x80131522.

    In meiner Appxmanifest habe ich folgende Einträge gesetzt:

    <Extension Category="windows.backgroundTasks" EntryPoint="Threading.lib.BackgroundTask">
              <BackgroundTasks>
                <Task Type="general"/>
                <Task Type="systemEvent"/>
              </BackgroundTasks>
    </Extension>
    <Extensions>
        <Extension Category="windows.activatableClass.inProcessServer">
          <InProcessServer>
            <Path>CLRHost.dll</Path>
            <ActivatableClass ActivatableClassId="Threading.lib.BackgroundTask" ThreadingModel="both" />
          </InProcessServer>
        </Extension>
    </Extensions>

    In meinem ViewModel mache ich etwa folgendes:

            public async void InitBuilder()
            {
                
    
                builder.Name = "Mein Hintergrundtask";
                builder.TaskEntryPoint = typeof(BackgroundTask).FullName;
                this.AppTrigger = new ApplicationTrigger();
    
                builder.SetTrigger(this.AppTrigger);
    
                var requestStatus = await BackgroundExecutionManager.RequestAccessAsync();
    
                Tools.ShowToastNotification("RequestStatus", requestStatus.ToString()); // Result ist AllowedSubjectToSystemPolicy
    
                this.UpdateTasks();
                
            }
    
            internal async Task AddTaskAsync()
            {
                
    
                BackgroundTaskRegistration task = builder.Register();
                task.Completed += new BackgroundTaskCompletedEventHandler(OnCompleted);
    
                var result = await this.AppTrigger.RequestAsync();
    
                Tools.ShowToastNotification("Task gestartet", result.ToString());
    
                this.UpdateTasks();
               
            }

    Und meine BackgroundTask Klasse:

    namespace Threading.lib
    {
        public sealed class BackgroundTask : IBackgroundTask
        {
            BackgroundTaskDeferral _deferral;
            public void Run(IBackgroundTaskInstance taskInstance)
            {
                _deferral = taskInstance.GetDeferral();
    
                Tools.ShowToastNotification("BackgroundTask", "Der Task wurde gestartet!");
    
                // Fake rechenintensiver Code
                new ManualResetEvent(false).WaitOne(1000);
    
    
                _deferral.Complete();
            }
        }
    }

    Jemand eine Idee, wieso der Task nicht registriert werden kann?

    Liebe Grüße 

    Marc



    • Bearbeitet Marc De Donnerstag, 4. Juli 2019 11:41
    Donnerstag, 4. Juli 2019 11:08
  • Da hab ich mir die Dokumentation nicht ganz durchgelesen. Ich hatte den BackgroundTask im selben Projekt und nicht in einer Windows-Runtime Komponente. Zumindest dieses Problem hat sich erledigt.
    Donnerstag, 4. Juli 2019 12:04
  • Wie Du nun rausgefunden hast wird der Task als Windows Runtime Komponente erstellt, Resultat ist eine Winmd. Diese wird von Windows ausgeführt und nicht deiner App. Man kann mit diesem Task nicht so kommunizieren wie mit irgendeiner Klasse innerhalb der App. Man sollte den Task als eigenständiges Program ansehen. Kommunikationsmöglichkeiten sind in der Doku beschrieben.

    Wie ich oben schon geschrieben habe, kommuniziere ich mit den Task nur über eine DB oder ein Textdatei (Json/XML)


    Gruß Thomas
    13 Millionen Schweine landen jährlich im Müll
    Dev Apps von mir: UWP Segoe MDL2 Assets, UI Strings

    Donnerstag, 4. Juli 2019 13:58