2008-10-31 4 views
2

Я работаю над библиотекой C#, которая разгружает определенные рабочие задачи на графический процессор, используя CUDA NVIDIA. Примером этого является добавление двух массивов вместе с использованием методов расширения:Как заблокировать до завершения асинхронного задания

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

Работа в этом коде делается на GPU. Тем не менее, я хотел бы, чтобы это выполнялось асинхронно, так что только тогда, когда нужен результат, будет выполняться код на блоке CPU (если результат еще не закончен на графическом процессоре). Для этого я создал класс ExecutionResult, который скрывает асинхронное выполнение. При использовании это выглядит следующим образом:

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

В последней строке программа блокирует, если данные еще готовы. Я не уверен в лучшем способе реализации этого поведения блокировки внутри класса ExecutionResult, так как я не очень разбираюсь в синхронизации потоков и таких вещей.

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; 
    } 
} 

Действие, переданное конструктору, представляет собой асинхронный метод, который выполняет фактическую работу с графическим процессором. Вложенным действием является асинхронный метод обратного вызова.

Моей главной задачей является то, как наилучшим образом/наиболее элегантно обрабатывать ожидания, выполненные в конвертере, но также, если есть более подходящие способы атаки на проблему в целом. Просто оставьте комментарий, если есть что-то, что мне нужно, чтобы уточнить или объяснить дальше.

ответ

1

Удивительно, если вы не можете использовать обычный Delegate.BeginInvoke/Delegate.EndInvoke здесь? Если нет, то ждать ручки (например, ManualResetEvent) может быть вариант:

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); 

} 

Обратите внимание, что вы можете сделать это только с помощью объекта, если вы действительно хотите:

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); 
     } 
    } 

} 
6

Это не ясно мне, насколько это структура, которую вы реализуете, и сколько вы звоните в другой код, но я, по мере возможности, следовал бы "normal" async pattern в .NET.

3

Решение, которое я нашел, состоит в том, чтобы передать функцию конструктору ExecutionResult, который выполняет две функции. При запуске он запускает асинхронную работу и, кроме того, он возвращает другую функцию, которая возвращает желаемый результат:

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(); 
} 

Функциональных блоков «» GetResult, пока данные не были вычислены и извлекается из GPU. Это хорошо работает с тем, как структурирован API-интерфейс драйвера CUDA.

Это довольно чистое и простое решение. Поскольку C# позволяет анонимные функции, которые будут созданы с доступом к локальной области видимости, это просто вопрос замены блокирующей части методы передается конструктору ExecutionResult таким образом, что ...

... 

    status = LaunchGrid(func, length); 

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

    return c; 
} 

становится ...

... 

    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; 
    }; 
} 
0

Использование cudaThreadSyncronize() или memcpy() позволяет выполнить синхронные операции - подходит для Invoke().

CUDA также позволяет запросить асинхронную передачу данных с помощью callAsync()/sync() - для Begin/EndInvoke(), используя callAsync().

Смежные вопросы