2 Stimmen

Blockieren, bis ein asynchroner Auftrag beendet ist

Ich arbeite an einer C#-Bibliothek, die bestimmte Arbeitsaufgaben mit NVIDIAs CUDA auf den Grafikprozessor verlagert. Ein Beispiel dafür ist das Zusammenfügen von zwei Arrays mithilfe von Erweiterungsmethoden:

float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
float[] c = a.Add(b);

Die Arbeit in diesem Code wird von der GPU erledigt. Ich möchte jedoch, dass die Ausführung asynchron erfolgt, so dass der auf der CPU laufende Code nur dann blockiert wird, wenn das Ergebnis benötigt wird (wenn das Ergebnis auf der GPU noch nicht fertig ist). Um dies zu erreichen, habe ich eine ExecutionResult-Klasse erstellt, die die asynchrone Ausführung verbirgt. In der Anwendung sieht dies wie folgt aus:

float[] a = new float[]{ ... }
float[] b = new float[]{ ... }
ExecutionResult res = a.Add(b);
float[] c = res; //Implicit converter

In der letzten Zeile blockiert das Programm, wenn die Daten noch nicht fertig sind. Ich bin mir nicht sicher, wie ich dieses blockierende Verhalten am besten in der Klasse ExecutionResult implementieren kann, da ich nicht viel Erfahrung mit der Synchronisierung von Threads und solchen Dingen habe.

public class ExecutionResult<T>
{
    private T[] result;
    private long computed = 0;

    internal ExecutionResult(T[] a, T[] b, Action<T[], T[], Action<T[]>> f)
    {
        f(a, b, UpdateData); //Asych call - 'UpdateData' is the callback method
    }

    internal void UpdateData(T[] data)
    {
        if (Interlocked.Read(ref computed) == 0)
        {
            result = data;
            Interlocked.Exchange(ref computed, 1);
        }
    }

    public static implicit operator T[](ExecutionResult<T> r)
    {
        //This is obviously a stupid way to do it
        while (Interlocked.Read(ref r.computed) == 0)
        {
            Thread.Sleep(1);
        }

        return result;
    }
}

Die an den Konstruktor übergebene Aktion ist eine asynchrone Methode, die die eigentliche Arbeit auf der GPU ausführt. Die verschachtelte Action ist die asynchrone Callback-Methode.

Meine Hauptsorge ist, wie man am besten/elegantesten mit dem Warten im Konverter umgeht, aber auch, ob es geeignetere Wege gibt, das Problem als Ganzes anzugehen. Hinterlassen Sie einfach einen Kommentar, wenn es etwas gibt, das ich weiter ausarbeiten oder erklären muss.

3voto

Morten Christiansen Punkte 18236

Die Lösung, die ich für dieses Problem gefunden habe, besteht darin, eine Funktion an den ExecutionResult-Konstruktor zu übergeben, die zwei Dinge tut. Wenn sie ausgeführt wird, startet sie die asynchrone Arbeit und gibt darüber hinaus eine weitere Funktion zurück, die das gewünschte Ergebnis liefert:

private Func<T[]> getResult;

internal ExecutionResult(T[] a, T[] b, Func<T[], T[], Func<T[]>> asynchBinaryFunction)
{
   getResult = asynchUnaryFunction(a);
}

public static implicit operator T[](ExecutionResult<T> r)
{
    return r.getResult();
}

Die Funktion "getResult" blockiert, bis die Daten berechnet und von der GPU abgerufen wurden. Dies funktioniert gut mit der Struktur der CUDA-Treiber-API.

Es ist eine recht saubere und einfache Lösung. Da in C# anonyme Funktionen mit Zugriff auf den lokalen Bereich erstellt werden können, muss lediglich der blockierende Teil einer an den ExecutionResult-Konstruktor übergebenen Methode ersetzt werden, damit...

    ...

    status = LaunchGrid(func, length);

    //Fetch result
    float[] c = new float[length];
    status = CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize);
    status = Free(ptrA, ptrB);

    return c;
}

wird...

    ...

    status = LaunchGrid(func, length);

    return delegate
    {
        float[] c = new float[length];
        CUDADriver.cuMemcpyDtoH(c, ptrA, byteSize); //Blocks until work is done
        Free(ptrA, ptrB);
        return c;
    };
}

1voto

Marc Gravell Punkte 970173

Ich frage mich, ob Sie nicht die normale Delegate.BeginInvoke / Delegate.EndInvoke hier? Wenn nicht, dann wird ein Wartehandle (wie z.B. ein ManualResetEvent ) könnte eine Option sein:

using System.Threading;
static class Program {
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWork);

        System.Console.WriteLine("Main: waiting");
        wait.WaitOne();
        System.Console.WriteLine("Main: done");
    }
    static void DoWork(object state)
    {
        System.Console.WriteLine("DoWork: working");
        Thread.Sleep(5000); // simulate work
        System.Console.WriteLine("DoWork: done");
        wait.Set();
    }
    static readonly ManualResetEvent wait = new ManualResetEvent(false);

}

Beachten Sie, dass Sie dies auch mit object tun können, wenn Sie wirklich wollen:

using System.Threading;
static class Program {
    static void Main()
    {
        object syncObj = new object();
        lock (syncObj)
        {
            ThreadPool.QueueUserWorkItem(DoWork, syncObj);

            System.Console.WriteLine("Main: waiting");
            Monitor.Wait(syncObj);
            System.Console.WriteLine("Main: done");
        }
    }
    static void DoWork(object syncObj)
    {

        System.Console.WriteLine("DoWork: working");
        Thread.Sleep(5000); // simulate work
        System.Console.WriteLine("DoWork: done");
        lock (syncObj)
        {
            Monitor.Pulse(syncObj);
        }
    }

}

0voto

Danny Varod Punkte 16437

Mit cudaThreadSyncronize() oder memcpy() können Sie synchrone Operationen durchführen - geeignet für Invoke().

Mit CUDA können Sie auch eine asynchrone Speicherübertragung mit callAsync() / sync() anfordern - geeignet für Begin/EndInvoke() mit callAsync().

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X