Laden...

[gelöst] Zeitintensive Messhardware-Aufgabe -> Task, Thread oder doch Backgroundworker

Erstellt von ill_son vor 4 Jahren Letzter Beitrag vor 4 Jahren 2.323 Views
I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 4 Jahren
[gelöst] Zeitintensive Messhardware-Aufgabe -> Task, Thread oder doch Backgroundworker

Hallo,

in meiner Anwendung soll eine Kommunikation mit einer Messhardware stattfinden. Das Ganze sieht so aus, dass nacheinander verschiedene Signale erzeugt werden und jeweils eine Messung durchgeführt wird. Gesamtdauer etwa halbe bis eine Minute. Zwischendurch wären Statusmeldungen mit Zwischenergebnissen ganz nett. Jetzt stellt sich mir die Frage der Implementierung. Thread mit entsprechenden Events oder await Task mit Progress? Ich habe mal irgendwo gelesen, Backgroundworker sei seit async Task "obsolet". Aber mal grundsätzlich, wann Thread und wann Task?

Grüße, Alex

Final no hay nada más

W
955 Beiträge seit 2010
vor 4 Jahren

Wäre ne schöne Aufgabe für ReactiveExtensions.

A
764 Beiträge seit 2007
vor 4 Jahren

Hallo ill_son

Benutze Tasks mit async/await, es sei denn du weißt, dass du für einen bestimmten Anwendungsfall etwas anders brauchst.

Ansonsten das was witte gesagt hat: "Wäre ne schöne Aufgabe für ReactiveExtensions."

Gruß
Alf

6.911 Beiträge seit 2009
vor 4 Jahren

Hallo ill_son,

grundsätzlich, wann Thread und wann Task?

Grundsätzlich (;-)) hatten wir diese Frage schon öfters, daher hier nur kurz der Rest bitte via (Foren-)suche.

Thread ist relativ hardwarenahe und führt einen bestimmten Code aus.
Für kurze Aufgaben (höchsten ein paar Sekunden) wird ein Thread idealerweise aus dem ThreadPool verwendet, da das Erstellen eines Threads aufwändig ist.
Für längere Aufgaben sollte der ThreadPool vermieden werden und ein eigenständiger Thread verwendet werden. Dies v.a. da der ThreadPool für solche Anforderungen nicht konzipiert wurde und somit nicht optimal arbeiten kann.

Task ist eine Abstraktion, die auf Threads aufbaut, und es dem Benutzer vereinfachen soll mit asynchronen Aufgaben zu arbeiten. Dies v.a. durch die mit C# 5 eingeführten Schlüsselwörter async / await und durch etliche API im Framework die mit Task arbeiten.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 4 Jahren

Hallo,

danke für Eure Antworten.

@gfoidl: Das heißt für mich, dass ich, anders als Alf sagt, doch besser einen eigenen Thread erstellen sollte, denn wie hier beschrieben, nutzen Tasks den Thread Pool. Stimmt das so?

Final no hay nada más

16.806 Beiträge seit 2008
vor 4 Jahren

Schau Dir mal an, was Threads sind und was Tasks sind.

grundsätzlich, wann Thread und wann Task?
Grundsätzlich (;-)) hatten wir diese Frage schon öfters, daher hier nur kurz der Rest bitte via
>
.

Es gibt in .NET kaum noch einen Grund auf Threads zu setzen, weil prinzipiell fast alles mit Tasks umsetzbar ist.
In Deinem Fall hast Du einfach nur eine Long Running Operation, die Du - so wie ich das sehe - mit einem Long Running Task problemlos laufen lassen kannst.
Und dafür gibts Task.Run() (verwendet den ThreadPool, was in meinen Augen aber hier im Grunde erstmal egal ist).

6.911 Beiträge seit 2009
vor 4 Jahren

Hallo ill_son,

nutzen Tasks den Thread Pool. Stimmt das so?

Bedingt.

Task.Run, Task.Factorry.StartNew nutzen standardmäßig den TaskScheduler.Default und der arbeitet mit dem ThreadPool.
Es kann auch ein anderer TaskScheduler verwendet werden, der mit dem ThreadPool nichts zu tun hat.
Bei StartNew hängt es auch von den Flags, die der Fabrik übergeben werden, ab ob der Task im ThreadPool landet od. ein dezidierter Thread (TaskCreationOptions.LongRunning) erstellt wird.

Task.ContinueWith, async / await können ggf. auch auf den ThreadPool verzichten, falls z.B. bei await der Task bereits fertig ist, dann wird einfach im aktuellen Thread fortgefahren. Od. bei I/O-Abschlussthreads wird der (CPU-) ThreadPool auch nicht verwendet.

Weiters lassen sich Tasks mit TaskCreationSource<T> erstellen, dabei wird ganz auf den ThreadPool verzichtet.

Und das einfachste zum Schluss: Task<int> task = Task.FromResult(42); ist ein gültiger Task, der ebenfalls ohne ThreadPool auskommt.

Hab mir deinen Link nicht angeschaut, aber ich hoffe dass auf diese Details zumindest hingewiesen wurde 😉

Das heißt für mich, ... doch besser einen eigenen Thread erstellen sollte

Ich würde es schon via Task abhandeln, nur ob der ThreadPool bzw. ein eigenständiger Thread (den der Task kapselt) verwendet wird weiß ich (noch) nicht.

verschiedene Signale erzeugt werden und jeweils eine Messung durchgeführt wird. Gesamtdauer etwa halbe bis eine Minute. Zwischendurch wären Statusmeldungen mit Zwischenergebnissen ganz nett.

Erzähl mal ein bischer mehr über den Aufbau.
Wie wird die Messung angestossen / durchgeführt?
Wie wird das Messergebnis erhalten?
Gibt die Messhardware Statusmeldungen zurück od. meinst du einfach in der UI einen Spinner, etc. anzeigen?
Wird mit einem bestimmte Protokoll (z.B. auf Basis TCP) mit der Messhardware kommuniziert?

Je nachdem gibt es u.U. elegantere Lösungen als einfach einen Task / Thread zu verwenden.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 4 Jahren

Hallo gfoidl,

erst einmal danke für den ausführlichen Exkurs.

ich arbeite mit dem DAQmx von National Instruments, wenn dir das ein Begriff ist. Dieser unterstützt auch .NET. Ich habe eine Messkarte, mit dieser erzeuge ich einen Sinus unterschiedlicher Frequenz (Sweep) und messe für jede Frequenz einige Perioden. Das Erzeugen des Signals benötigt kaum Zeit, weil es auf der Karte passiert. Man muss den Vorgang nur initialisieren, aber da die Frequenzen recht niedrig sind, benötigt die Funktion, die die Messwerte holt ihre Zeit. Daten werden als double[,] zurückgegeben.

Im Prinzip durchlaufe ich eine for-Schleife derart:


public async Task<List<FrequencyStepResult>> Sweep(CancellationTokenSource cts, IProgress<FrequencyStepResult> progress)
{
    List<FrequencyStepResult> result = new List<FrequencyStepResult>();
    if (IsHardwareReady)
        await Task.Run(() =>
        {
            for (int i = 0; i < FrequencySteps.Count && !cts.IsCancellationRequested; i++)
            {
                 FrequencyStepResult stepResult = PerformFrequencyStep(OutputMagnitude, FrequencySteps[i]);
                 result.Add(stepResult);
                 progress?.Report(stepResult);
             }
       });

    return result;
}
        
private FrequencyStepResult PerformFrequencyStep(double magnitude, double frequency)
{
      double duration = 5 / frequency; //at leat 5 periodes
      if (duration < 1)
           duration = 1;

      int sampleRate = (int)(20 * frequency);
      if (sampleRate < 1000)
           sampleRate = 1000;

      int samples = (int)Math.Ceiling(duration * sampleRate);

       _DAQmx.StartSinusOutput(magnitude, frequency);
            
       double[,] data = _DAQmx.ReadSequence(sampleRate, samples);
       
       _DAQmx.StopOutput();

       return new FrequencyStepResult(frequency, data);
}

Grüße, Alex

Final no hay nada más

6.911 Beiträge seit 2009
vor 4 Jahren

Hallo ill_son,

welcher Typ ist _DAQmx genau?
Ein paar Methoden von DAQmx unterstützen asynchrone Vorgänge (nach dem (alten) Asynchronous Programming Model (APM)), darauf lässt sich idealerweise aufbauen.

(Schade dass DAQmx .NET Core 3.0 nicht unterstützt, denn mit "async streams" (IAsyncEnumerable) wäre das angenehm zu lösen).){gray}

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 4 Jahren

Hallo gfoidl,

_Daqmx ist die Instanz einer Klasse, die ich geschrieben habe. Sie kapselt den Zugriff auf die Karte und stellt mir die Funktionalitäten bereit, die ich für die Messung benötige. Unter DAQmx und APM habe ich auf der Seite von NI was gefunden. Ich schaue mir das mal an, vielleicht ist das ein Einstieg.

Grüße, Alex

Final no hay nada más

6.911 Beiträge seit 2009
vor 4 Jahren

Hallo ill_son,

ja genau so gehts (nicht nur vom Code her, sonder auch wegen deiner Recherche dazu 👍).

Mit TPL and Traditional .NET Framework Asynchronous Programming lässt sich das dann in einem Adapter schön kapseln. Ich stell mir das grob so vor:


public interface IMeasureDevice
{
    Task WaitForHardwareReadyAsync();
    Task StartSinusOutputAsync(double magnitude, double frequency);
    Task<double[,]> ReadSequenceAsync(int sampelRate, int sampleCount);
    Task StopOutputAsync();
}

public class Wobbler
{
    private const int MinPeriodCount = 5;
    private const int SampleRate     = 20;

    private readonly IMeasureDevice _measureDevice;

    public Wobbler(IMeasureDevice measureDevice)
    {
        _measureDevice = measureDevice ?? throw new ArgumentNullException(nameof(measureDevice));
    }

    public async Task<IReadOnlyCollection<FrequencyStepResult>> SweepAsync(
        double                         magnitude,
        IEnumerable<double>            frequencySteps,
        IProgress<FrequencyStepResult> progress,
        CancellationToken              ct = default)
    {
        var result = new List<FrequencyStepResult>();

        await _measureDevice.WaitForHardwareReadyAsync().ConfigureAwait(false);

        foreach (double frequency in frequencySteps)
        {
            if (ct.IsCancellationRequested)
                break;

            var frequencyResult = await this.PerformFrequencyStep(magnitude, frequency, ct).ConfigureAwait(false);
            result.Add(frequencyResult);
            progress?.Report(frequencyResult);
        }

        return result.AsReadOnly();
    }

    private async Task<FrequencyStepResult> PerformFrequencyStep(double magnitude, double frequency, CancellationToken ct)
    {
        double duration = Math.Max(1, MinPeriodCount / frequency);
        int sampleRate  = Math.Max(1000, (int)(SampleRate * frequency));
        int sampleCount = (int)Math.Ceiling(sampleRate * duration);

        await _measureDevice.StartSinusOutputAsync(magnitude, frequency).ConfigureAwait(false);
        double[,] data = await _measureDevice.ReadSequenceAsync(sampleRate, sampleCount).ConfigureAwait(false);
        await _measureDevice.StopOutputAsync().ConfigureAwait(false);

        return new FrequencyStepResult(frequency, data);
    }
}

public readonly struct FrequencyStepResult
{
    public double Frequency { get; }
    public double[,] Data   { get; }

    public FrequencyStepResult(double frequency, double[,] data)
    {
        if (frequency < 0) throw new ArgumentOutOfRangeException("negative frequency not feasable");

        this.Frequency = frequency;
        this.Data      = data ?? throw new ArgumentNullException(nameof(data));
    }
}

internal class DAQmxMeasureDeviceAdapter : IMeasureDevice
{
    public Task WaitForHardwareReadyAsync()
        => Task.Factory.FromAsync(DAQmx.BeginWaitForHardware(), DAQmx.EndWaitForHardware);

    public Task StartSinusOutputAsync(double magnitude, double frequency)
        => Task.Factory.FromAsync(DAQmx.BeginSendSignal(magnitude, frequency), DAQmx.EndSendSignal);

    public Task<double[,]> ReadSequenceAsync(int sampelRate, int sampleCount)
        => Task.Factory.FromAsync(DAQmx.BeginReadSequence(sampelRate, sampleCount), DAQmx.EndReadSequence);

    public Task StopOutputAsync()
        => Task.Factory.FromAsync(DAQmx.BeginStopOutput(), DAQmx.EndStopOutput);
}

Im Adapter DAQmxMeasureDeviceAdapter hab ich die Methoden des fiktiven Typs DAQmx nur angenommen. Diese sind durch die tatsächlichen Methoden zu ersetzen -- das schafftst du sicher.

Die Verwendung wäre dann z.B.


class Program
{
    static async Task Main(string[] args)
    {
        IMeasureDevice measureDevice                    = new DAQmxMeasureDeviceAdapter();
        var wobbler                                     = new Wobbler(measureDevice);
        var progress                                    = new Progress<FrequencyStepResult>(OnProgress);
        IReadOnlyCollection<FrequencyStepResult> result = await wobbler.SweepAsync(10, GetFrequencySteps(), progress);
    }

    private static void OnProgress(FrequencyStepResult frequencyStepResult)
    {
        // Use result
    }

    private static IEnumerable<double> GetFrequencySteps()
    {
        double currentFrequency = 10;

        for (int i = 1; i < 10; ++i)
        {
            yield return currentFrequency;
            currentFrequency *= 10;
        }
    }
}

Als Nebenbemerkung:
double[,] wenn du diesen rectangular array vermeiden kannst wäre es noch besser, denn diese sind sehr unperformant, da die CLR Methoden-Aufrufe für die Zugriffe verwenden muss. Jagged arrays werden von der CLR direkt unterstützt in Form von sz-Arrays und dort sind die Zugriffe direkte Speicherreferenzen.
Od. statt des Arrays kann auch eine eigenen Struktur verwendet werden, die dann in eine passende Collection gepackt wird.

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

I
ill_son Themenstarter:in
227 Beiträge seit 2009
vor 4 Jahren

Hallo gfoidl,

vielen Dank für deine Mühe. Damit, denke ich, sollte das Thema gelöst sein. Zu den Arrays, ich hab schon gemerkt, dass die nicht so "handy" sind. Leider liefern die Treiberfunktionen die Daten in diesem Format. Ich hab mir aber was gebastelt/recherchiert, dass mit Hilfe von LINQ praktische Listen daraus macht.

Falls jemand mal in die Verlegenheit kommen sollte:


private double[] GetColumn(double[,] array, int columnNumber)
{
    return Enumerable.Range(0, array.GetLength(0)).Select(x => array[x, columnNumber]).ToArray();
}

public double[] GetRow(double[,] array, int rowNumber)
{
    return Enumerable.Range(0, array.GetLength(1)).Select(x => array[rowNumber, x]).ToArray();
}

Grüße und danke nochmals,

Alex

Final no hay nada más