Pergunta

Vamos dizer que tenho uma classe que implementa a IDisposable interface. Algo parecido com isto:

http://www.flickr.com/photos/garthof/3149605015/

MyClass usa alguns recursos não gerenciados, daí a Dispose () método de IDisposable libera esses recursos. MyClass deve ser usado como este:

using ( MyClass myClass = new MyClass() ) {
    myClass.DoSomething();
}

Agora, eu quero implementar um método que chama DoSomething () de forma assíncrona. Eu adicionar um novo método para MyClass :

http://www.flickr.com/photos/garthof/3149605005/

Agora, do lado do cliente, MyClass deve ser usado como este:

using ( MyClass myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

No entanto, se eu não fazer qualquer outra coisa, isso poderia falhar como o objeto myClass pode ser eliminado antes de DoSomething () é chamado (e jogar um inesperado < strong> ObjectDisposedException ). Assim, a chamada para o Dispose () método (seja implícita ou explícita) deve ser adiada até que a chamada assíncrona para DoSomething () é feito.

Eu acho que o código no Dispose () método deve ser executado de forma assíncrona, e apenas uma vez todas as chamadas assíncronas são resolvidos . Eu gostaria de saber o que poderia ser a melhor maneira de conseguir isso.

Graças.

NOTA: Por uma questão de simplicidade, eu não entraram em detalhes de como método Dispose () é implementado. Na vida real eu costumo seguir a Descarte padrão .


UPDATE: Muito obrigado por suas respostas. Eu aprecio o seu esforço. Como chakrit comentou , eu preciso que várias chamadas para o async DoSomething pode ser feita . Idealmente, algo como isto deve funcionar bem:

using ( MyClass myClass = new MyClass() ) {

    myClass.AsyncDoSomething();
    myClass.AsyncDoSomething();

}

Vou estudar o semáforo de contagem, parece que eu estou procurando. Também poderia ser um problema de design. Se eu achar conveniente, vou compartilhar com vocês algumas bits do caso real eo que MyClass realmente faz.

Foi útil?

Solução 6

Assim, a minha ideia é manter quantas AsyncDoSomething () estão pendentes para ser concluída, e só dispor quando esta contagem chega a zero. Minha abordagem inicial é:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        pendingTasks++;
        AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            if ( pendingTasks == 0 ) {
                return;
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
        caller.EndInvoke( ar );
        pendingTasks--;
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

Alguns problemas podem ocorrer se dois ou mais threads tentar ler / escrever o pendingTasks variável ao mesmo tempo, de modo que o Bloqueio palavra-chave deve ser usado para evitar condições de corrida:

public class MyClass : IDisposable {

    private delegate void AsyncDoSomethingCaller();
    private delegate void AsyncDoDisposeCaller();

    private int pendingTasks = 0;
    private readonly object lockObj = new object();

    public DoSomething() {
        // Do whatever.
    }

    public AsyncDoSomething() {
        lock ( lockObj ) {
            pendingTasks++;
            AsyncDoSomethingCaller caller = new AsyncDoSomethingCaller();
            caller.BeginInvoke( new AsyncCallback( EndDoSomethingCallback ), caller);
        }
    }

    public Dispose() {
        AsyncDoDisposeCaller caller = new AsyncDoDisposeCaller();
        caller.BeginInvoke( new AsyncCallback( EndDoDisposeCallback ), caller);
    }

    private DoDispose() {
        WaitForPendingTasks();

        // Finally, dispose whatever managed and unmanaged resources.
    }

    private void WaitForPendingTasks() {
        while ( true ) {
            // Check if there is a pending task.
            lock ( lockObj ) {
                if ( pendingTasks == 0 ) {
                    return;
                }
            }

            // Allow other threads to execute.
            Thread.Sleep( 0 );
        }
    }

    private void EndDoSomethingCallback( IAsyncResult ar ) {
        lock ( lockObj ) {
            AsyncDoSomethingCaller caller = (AsyncDoSomethingCaller) ar.AsyncState;
            caller.EndInvoke( ar );
            pendingTasks--;
        }
    }

    private void EndDoDisposeCallback( IAsyncResult ar ) {
        AsyncDoDisposeCaller caller = (AsyncDoDisposeCaller) ar.AsyncState;
        caller.EndInvoke( ar );
    }
}

Eu vejo um problema com esta abordagem. Como a liberação de recursos é de forma assíncrona feito, algo como este trabalho poder:

MyClass myClass;

using ( myClass = new MyClass() ) {
    myClass.AsyncDoSomething();
}

myClass.DoSomething();

Quando o comportamento esperado deve ser o de lançar um ObjectDisposedException quando DoSomething () é chamado fora do usando cláusula. Mas eu não acho isso ruim o suficiente para repensar esta solução.

Outras dicas

Parece que você está usando o padrão assíncrono baseado em eventos ( veja aqui para mais informações sobre o .NET assíncrona padrões ), de modo que você normalmente tem é um evento na classe que é acionado quando a operação assíncrona for concluída chamado DoSomethingCompleted (note que AsyncDoSomething deve realmente ser chamado DoSomethingAsync a seguir o padrão corretamente ). Com este evento exposto você poderia escrever:

var myClass = new MyClass();
myClass.DoSomethingCompleted += (sender, e) => myClass.Dispose();
myClass.DoSomethingAsync();

A outra alternativa é usar o padrão IAsyncResult, onde você pode passar um delegado que chama o método dispose ao parâmetro AsyncCallback (mais informações sobre este padrão está na página acima também). Neste caso, você teria que BeginDoSomething e EndDoSomething métodos em vez de DoSomethingAsync, e chamaria isso de algo como ...

var myClass = new MyClass();
myClass.BeginDoSomething(
    asyncResult => {
                       using (myClass)
                       {
                           myClass.EndDoSomething(asyncResult);
                       }
                   },
    null);        

Mas do jeito que você fazê-lo, você precisa de um caminho para o chamador para ser notificado de que a operação assíncrona foi concluída para que ele possa alienar o objeto no tempo correto.

métodos

assíncronas geralmente têm um retorno de chamada que lhe permite fazer fazer alguma ação sobre completition. Se este é seu caso, seria algo como isto:

// The async method taks an on-completed callback delegate
myClass.AsyncDoSomething(delegate { myClass.Dispose(); });

Uma outra maneira de contornar isso é um invólucro assíncrona:

ThreadPool.QueueUserWorkItem(delegate
{
    using(myClass)
    {
        // The class doesn't know about async operations, a helper method does that
        myClass.DoSomething();
    }
});

Eu não iria alterar o código de alguma forma para permitir dispõe assíncronos. Em vez disso gostaria de ter certeza quando a chamada para AsyncDoSomething é feita, ela terá uma cópia de todos os dados de que necessita para executar. Esse método deve ser responsável pela limpeza todos se seus recursos.

Você pode adicionar um mecanismo de retorno de chamada e passar uma função de limpeza como uma chamada de retorno.

var x = new MyClass();

Action cleanup = () => x.Dispose();

x.DoSomethingAsync(/*and then*/cleanup);

mas isso representaria problema se você deseja executar várias chamadas assíncronas ao largo da mesma instância do objeto.

Uma forma seria a de implementar um simples contagem do semáforo com o Semaphore classe para contar o número de executar trabalhos assíncronos.

Adicione o contador para MyClass e em cada chamadas AsyncWhatever incrementar o contador, em saídas decerement-lo. Quando o semáforo é 0, em seguida, a classe está pronto para ser descartado.

var x = new MyClass();

x.DoSomethingAsync();
x.DoSomethingAsync2();

while (x.RunningJobsCount > 0)
    Thread.CurrentThread.Sleep(500);

x.Dispose();

Mas duvido que seria a maneira ideal. Eu cheiro um problema de design. Talvez um re-pensamento de design MyClass poderia evitar isso?

Você poderia compartilhar alguns pouco da implementação MyClass? O que é suposto fazer?

Considero lamentável que a Microsoft não requerem como parte do contrato IDisposable que implementações deve permitir Dispose a ser chamado a partir de qualquer contexto de segmentação, uma vez que não há nenhuma maneira sane a criação de um objeto pode forçar a existência continuada do rosqueamento contexto no qual ele foi criado. É possível projetar o código para que o segmento que cria um objeto de alguma forma prestar atenção para o objeto se tornando obsoleta e pode Dispose em sua conveniência e de modo que quando o fio não é mais necessário para qualquer outra coisa que vai ficar por aqui até que todos os objetos apropriados têm foi Disposed, mas eu não acho que há um mecanismo padrão que não requer um comportamento especial por parte do segmento criando o Dispose.

Sua melhor aposta é provavelmente ter todos os objetos de interesse criado dentro de uma linha comum (talvez o segmento interface do usuário), tente garantia de que o segmento vai ficar em torno de toda a vida útil dos objetos de interesse e uso algo como Control.BeginInvoke para solicitar disposição dos objetos. Desde que nem a criação do objeto nem de limpeza irá bloquear por qualquer período de tempo, que pode ser uma boa abordagem, mas se qualquer operação poderia bloquear pode ser necessária uma abordagem diferente [talvez abrir um formulário manequim escondida com seu próprio segmento, para que se possa uso Control.BeginInvoke lá].

Como alternativa, se você tem controle sobre as implementações IDisposable, projetá-los para que eles possam seguramente ser demitido de forma assíncrona. Em muitos casos, que irá "apenas trabalho", desde que ninguém está tentando usar o item quando ele é descartado, mas que quase um dado adquirido. Em particular, com muitos tipos de IDisposable, há um perigo real de que várias instâncias objeto pode tanto manipular um recurso externo comum [por exemplo, um objeto pode realizar uma List<> de instâncias criadas, adicionar instâncias a essa lista quando estes são construídos, e casos em Remover na Dispose; Se as operações de lista não são sincronizados, uma Dispose assíncrona pode corromper a lista mesmo se o objecto se encontra disposta não é de outro modo em uso.

BTW, um padrão útil é para objetos para permitir dispor assíncrona, enquanto eles estão em uso, com a expectativa de que tal disposição irá causar quaisquer operações em curso para lançar uma exceção na primeira oportunidade conveniente. Coisas como soquetes trabalhar desta forma. Pode não ser possível para uma operação de leitura para ser saída precoce sem sair de seu soquete em um estado inútil, mas se o socket nunca vai ser usado de qualquer maneira, não há nenhum ponto para a leitura de manter à espera de dados se outro segmento determinou que ele deve desistir. IMHO, é assim que todos os objetos IDisposable devem se esforçar para se comportar, mas eu não conheço nenhum documento pedindo um padrão tão geral.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top