2012-02-13 32 views
12

Mi método original se parece a:¿Está bien derivar de Tarea TPL para devolver más detalles del método?

string DoSomeWork(); 

Método DoSomeWork se inicia un trabajo en otro hilo de ejecución y devuelve ID (justo cadena aleatoria). Más tarde puedo consultar resultados por ID de ejecución dada. El punto principal es hacer que la ID de ejecución esté disponible antes de que se complete el trabajo.

Ahora quiero cambiar la firma para devolver la tarea, por lo que el usuario puede esperar si así lo desea.

Task DoSomeWork(); 

Al mismo tiempo, todavía tengo que devolver el ID de ejecución (para fines de rastreo, por ejemplo) y veo pocas opciones. Primero, si el parámetro es out, el segundo es devolver tuple con ID de ejecución y tarea (en C# esto no parece una mejor opción), y tercero acerca de lo que realmente quiero preguntar.

¿Qué pasa si voy a crear la clase que va a derivar de la clase Task:

public class ExtendedTask : Task 
{ 
    public string ExecutionID {get; set;} 
} 

¿Este se ve bien? ¿O es mejor decidir otras opciones?

P.S. En BCL hay algunos derivados de clases de tareas.

ACTUALIZACIÓN, parece que no pude definir este enigg claro. Pero necesito acceder a ExecutionID antes de que se complete el trabajo y, por lo tanto, no puedo usar Task.Result.

Respuesta

12

no me personalmente extenderTask<T>, me componen en su lugar. De esta forma, no tendrá que preocuparse por ninguna API que solo devuelva Task<T>; puede simplemente envolver la tarea. Puede tener una propiedad que expone la tarea subyacente, y para la asincronía C# 5 puede implementar el patrón de espera en su propio tipo - pero me parece que crear su propio tipo derivado es probable para hacer más daño que bien Sin embargo, es sobre todo una corazonada.

Otra opción es trabajar al revés: almacene su estado adicional en la propiedad Task.AsyncState; para eso está ahí, después de todo.De esta forma, puede pasar fácilmente la tarea sin perder el contexto de ejecución del que lógicamente es parte.

+0

Gracias por 'Task.AsyncState' Yo no estaba al tanto. Solo estoy preocupado ¿por qué se opone? Alguien puede anularlo. –

+1

@MikeChaliy Está destinado a implementar IAsyncResult, y es de solo lectura. Nadie puede anularlo una vez que construya su tarea. Es necesario para construir la tarea utilizando uno de los constructores que toma acción '' en lugar de 'action con el fin de conseguir este conjunto ... –

+0

ups @ReedCopsey, sí, tienes razón. Bueno, se ve aún mejor entonces. –

11

Recomendaría usar Task<T>, ya que le permite "incrustar" la otra información en el Resultado de la tarea.

Por ejemplo, en su caso, podría tener sentido para tener algo como:

class ExecutionResult 
{ 
    public int ExecutionID { get; set; } 
    public string Result { get; set; } 
    // ... 
} 


public Task<ExecutionResult> DoSomeWork() 
{ 
    return Task.Factory.StartNew(() => 
    { 
      // Replace with real work, etc... 
      return new ExecutionResult { ExecutionID = 0, Result = "Foo" }; 
    }); 
} 

Editar en respuesta a los comentarios:

Si necesita los datos "antes" del Grupo de completa, y está tratando de acceder a esto para otros fines, recomendaría hacer una clase que contenga la Tarea y los otros datos, y devolverla, es decir:

class ExecutionResult 
{ 
    public int ExecutionID { get; private set; } 
    public Task<string> Result { get; private set; } 
    // ... Add constructor, etc... 
} 


public ExecutionResult DoSomeWork() 
{ 
    var task = Task.Factory.StartNew(() => 
    { 
      // Replace with real work, etc... 
      return "Foo"; 
    }); 

    return new ExecutionResult(1, task); // Make the result from the int + Task<string> 
} 

Esto todavía le permitirá acceder a la información sobre su proceso, y el Task/Task<T>.

+0

'Task.Result' estará disponible después de que se complete el trabajo. Necesito este valor antes. –

+1

@MikeChaliy Editado para mostrar cómo lo abordaría en este caso ... –

+0

Gracias por su respuesta. –

1

Si hace deciden heredar de Task o Task<TResult>, puede encontrarse con la frustración que el delegado Action<Object> o Func<Object,TResult> que proporciona el trabajo real para la tarea debe especificar en el momento de su tarea derivada de el objeto está construido y no puede ser cambiado más tarde. Esto es cierto aunque los constructores de la clase base no Start() la tarea recién creada, y de hecho no puede iniciarse hasta mucho más tarde, o nunca.

Esto dificulta el uso de una clase derivada de Task en situaciones donde las instancias deben crearse antes de que estén disponibles todos los detalles de su eventual trabajo.

Un ejemplo podría ser una red amorfa de conocidos Task<TResult> nodos que trabajan en un objetivo común de tal manera que el acceso de cada otros Result propiedades en un manera ad-hoc. La forma más sencilla de garantizar que puede Wait() en cualquier nodo arbitrario en la red es preconstruir todos ellos antes de iniciar cualquiera de ellos. Esto evita claramente el problema de intentar analizar las dependencias del gráfico de trabajo, y permite que los factores de tiempo de ejecución determinen cuándo, si y en qué orden se exigen los valores Result.

El problema aquí es que, para algunos de los nodos, es posible que no pueda proporcionar la función que define el trabajo en tiempo de construcción. Si la creación de la función lambda necesaria requiere el cierre de Result valores de otras tareas en la red, el Task<TResult> que proporciona el Result que queremos podría no haberse construido aún. Y aunque se haya construido anteriormente durante la fase de preconstrucción, no puede llamar al Start(), ya que podría incorporar dependencias en otros nodos que no lo hayan hecho. Recuerde, el objetivo de preconstruir la red fue evitar complejidades como estas.

Como si esto fuera poco, hay otras razones por las que no es conveniente tener que usar una función lambda para proporcionar la función deseada. Dado que se transfiere al constructor como argumento, la función no puede acceder al puntero this de la instancia de tarea eventual, lo que crea un código feo, especialmente si se considera que la lambda se define necesariamente bajo el alcance de, y posiblemente sobre el cierre inadvertido. -algunos punteros this no relacionados.

Podría seguir, pero la conclusión es que no debería tener que soportar la saturación del cierre del tiempo de ejecución y otras molestias al definir la funcionalidad extendida en una clase derivada. ¿No pierde eso el punto del polimorfismo? Sería más elegante definir el delegado de trabajo de una clase derivada de Task de la manera normal, es decir, una función abstracta en la clase base.

He aquí cómo hacerlo. El truco consiste en definir un constructor privado que cierra uno de sus propios argumentos. El argumento, inicialmente establecido en null, actúa como una variable de marcador de posición que puede cerrar para crear el delegado requerido por la clase base Task. Una vez que estás en el cuerpo del constructor, el puntero 'this' está disponible, por lo que puedes aplicar un parche al puntero de la función real.

Para derivado de 'tarea':

public abstract class DeferredActionTask : Task 
{ 
    private DeferredActionTask(DeferredActionTask _this) 
     : base(_ => ((Func<DeferredActionTask>)_)().action(), 
       (Func<DeferredActionTask>)(() => _this)) 
    { 
     _this = this; 
    } 
    protected DeferredActionTask() : this(null) { } 

    protected abstract void action(); 
}; 

Para derivado de 'Tarea <TResult>':

public abstract class DeferredFunctionTask<TResult> : Task<TResult> 
{ 
    private DeferredFunctionTask(DeferredFunctionTask<TResult> _this) 
     : base(_ => ((Func<DeferredFunctionTask<TResult>>)_)().function(), 
       (Func<DeferredFunctionTask<TResult>>)(() => _this)) 
    { 
     _this = this; 
    } 
    protected DeferredFunctionTask() : this(null) { } 

    protected abstract TResult function(); 
}; 

[Editar: simplificado]

Estas versiones simplificadas reducen aún más cierres extraños cerrando directamente sobre los casos derivados acción o función método. Esto también libera el AsyncState en la clase base en caso de que quiera usarlo. Difícilmente parece necesario ya que ahora tiene su propia clase derivada completa; en consecuencia, AsyncState no se pasa a la función de trabajo. Si lo necesita, siempre puede tomarlo de la propiedad en la clase base. Finalmente, los diversos parámetros opcionales ahora se pueden pasar a la clase base Task.

Para que deriva de 'tarea':

public abstract class DeferredActionTask : Task 
{ 
    private DeferredActionTask(Action _a, Object state, CancellationToken ct, TaskCreationOptions opts) 
     : base(_ => _a(), state, ct, opts) 
    { 
     _a = this.action; 
    } 

    protected DeferredActionTask(
      Object state = null, 
      CancellationToken ct = default(CancellationToken), 
      TaskCreationOptions opts = TaskCreationOptions.None) 
     : this(default(Action), state, ct, opts) 
    { 
    } 

    protected abstract void action(); 
}; 

Para que deriva de 'tareas <TResult>':

public abstract class DeferredFunctionTask<TResult> : Task<TResult> 
{ 
    private DeferredFunctionTask(Func<TResult> _f, Object state, CancellationToken ct, TaskCreationOptions opts) 
     : base(_ => _f(), state, ct, opts) 
    { 
     _f = this.function; 
    } 

    protected DeferredFunctionTask(
      Object state = null, 
      CancellationToken ct = default(CancellationToken), 
      TaskCreationOptions opts = TaskCreationOptions.None) 
     : this(default(Func<TResult>), state, ct, opts) 
    { 
    } 

    protected abstract TResult function(); 
}; 
0
private async DeferredFunctionTask<int> WaitForStart(CancellationTokenSource c, string serviceName) 
    { 


     var t = await Task.Run<int>(() => 
     { 
      int ret = 0; 
      for (int i = 0; i < 500000000; i++) 
      { 


       //ret += i; 
       //if (i % 100000 == 0) 
       // Console.WriteLine(i); 

       if (c.IsCancellationRequested) 
       { 
        return ret; 
       } 
      } 

      return ret; 

     }); 


     return t; 
    } 

error CS1983 El tipo de retorno de un asíncrono el método debe ser nulo, tarea o tarea

Cuestiones relacionadas