A palavra-chave wait em C # (.NET Async CTP) não é permitida em uma instrução de bloqueio.
Do MSDN :
Uma expressão de espera não pode ser usada em uma função síncrona, em uma expressão de consulta, no bloco catch ou finalmente de uma instrução de tratamento de exceção, no bloco de uma instrução de bloqueio ou em um contexto não seguro.
Suponho que isso seja difícil ou impossível para a equipe do compilador implementar por algum motivo.
Tentei uma solução alternativa com a instrução using:
class Async
{
public static async Task<IDisposable> Lock(object obj)
{
while (!Monitor.TryEnter(obj))
await TaskEx.Yield();
return new ExitDisposable(obj);
}
private class ExitDisposable : IDisposable
{
private readonly object obj;
public ExitDisposable(object obj) { this.obj = obj; }
public void Dispose() { Monitor.Exit(this.obj); }
}
}
// example usage
using (await Async.Lock(padlock))
{
await SomethingAsync();
}
No entanto, isso não funciona conforme o esperado. A chamada para Monitor.Exit em ExitDisposable.Dispose parece bloquear indefinidamente (na maioria das vezes) causando bloqueios, enquanto outros threads tentam adquirir o bloqueio. Suspeito que a falta de confiabilidade do meu trabalho e a razão pela qual as instruções de espera não são permitidas na instrução de bloqueio estejam de alguma forma relacionadas.
Alguém sabe por que esperar não é permitido no corpo de uma declaração de bloqueio?
fonte
Respostas:
Não, não é nada difícil ou impossível de implementar - o fato de você ter implementado você mesmo é uma prova disso. Pelo contrário, é uma ideia incrivelmente ruim e, portanto, não permitimos, para protegê-lo de cometer esse erro.
Correto, você descobriu por que tornamos ilegal. Aguardar dentro de uma fechadura é uma receita para produzir impasses.
Tenho certeza de que você pode ver o motivo: o código arbitrário é executado entre o tempo em que o aguardado retorna o controle ao chamador e o método é retomado . Esse código arbitrário pode estar desativando bloqueios que produzem inversões de ordem de bloqueio e, portanto, bloqueios.
Pior, o código pode continuar em outro encadeamento (em cenários avançados; normalmente você retoma o encadeamento que aguardava, mas não necessariamente); nesse caso, o desbloqueio desbloqueia um bloqueio em um encadeamento diferente do encadeamento que levou fora da fechadura. Essa é uma boa ideia? Não.
Observo que também é uma "pior prática" fazer um
yield return
dentro de umlock
, pela mesma razão. É legal fazer isso, mas eu gostaria que tivéssemos tornado ilegal. Não vamos cometer o mesmo erro de "aguardar".fonte
SemaphoreSlim.WaitAsync
classe foi adicionada à estrutura .NET bem depois que você postou esta resposta, acho que podemos assumir com segurança que isso é possível agora. Independentemente disso, seus comentários sobre a dificuldade de implementar essa construção ainda são totalmente válidos.Use o
SemaphoreSlim.WaitAsync
métodofonte
Stuff
eu não vejo qualquer maneira de contornar isso ...mySemaphoreSlim = new SemaphoreSlim(1, 1)
para funcionar comolock(...)
?Basicamente, seria a coisa errada a fazer.
Há duas maneiras este poderia ser implementadas:
Segure a trava, liberando-a apenas no final do bloco .
Essa é uma péssima idéia, pois você não sabe quanto tempo a operação assíncrona levará. Você só deve reter bloqueios por um período mínimo de tempo. Também é potencialmente impossível, pois um encadeamento possui um bloqueio, não um método - e você pode nem executar o restante do método assíncrono no mesmo encadeamento (dependendo do agendador de tarefas).
Solte o bloqueio no aguarde e recupere-o quando o aguardar retornar
Isso viola o princípio da IMO de menor espanto, em que o método assíncrono deve se comportar o mais próximo possível do código síncrono equivalente - a menos que você use
Monitor.Wait
em um bloqueio, espere possui a trava pela duração do bloco.Portanto, basicamente existem dois requisitos concorrentes aqui - você não deve tentar fazer o primeiro aqui e, se quiser adotar a segunda abordagem, poderá tornar o código muito mais claro, com dois blocos de bloqueio separados pela expressão de espera:
Portanto, ao proibi-lo de aguardar no próprio bloco de bloqueio, o idioma está forçando você a pensar no que realmente deseja fazer e tornando essa opção mais clara no código que você escreve.
fonte
SemaphoreSlim.WaitAsync
classe foi adicionada à estrutura .NET bem depois que você postou esta resposta, acho que podemos assumir com segurança que isso é possível agora. Independentemente disso, seus comentários sobre a dificuldade de implementar essa construção ainda são totalmente válidos.Esta é apenas uma extensão para esta resposta .
Uso:
fonte
try
bloco - se uma exceção acontece entreWaitAsync
etry
o semáforo nunca será liberado (impasse). Por outro lado, mover aWaitAsync
chamada para otry
bloco apresentará outro problema, quando o semáforo pode ser liberado sem que um bloqueio seja adquirido. Consulte o segmento relacionado onde esse problema foi explicado: stackoverflow.com/a/61806749/7889645Refere-se a http://blogs.msdn.com/b/pfxteam/archive/2012/02/12/10266988.aspx , http://winrtstoragehelper.codeplex.com/ , loja de aplicativos do Windows 8 e .net 4.5
Aqui está o meu ângulo sobre isso:
O recurso de linguagem assíncrona / aguardada facilita muitas coisas, mas também apresenta um cenário que raramente era encontrado antes de ser tão fácil usar chamadas assíncronas: reentrada.
Isso é especialmente verdadeiro para os manipuladores de eventos, porque para muitos eventos você não tem idéia do que está acontecendo depois que você retorna do manipulador de eventos. Uma coisa que pode realmente acontecer é que o método assíncrono que você está aguardando no primeiro manipulador de eventos é chamado de outro manipulador de eventos ainda no mesmo encadeamento.
Aqui está um cenário real que encontrei em um aplicativo da App Store do Windows 8: Meu aplicativo tem dois quadros: entrando e saindo de um quadro. Quero carregar / proteger alguns dados em um arquivo / armazenamento. Os eventos OnNavigatedTo / From são usados para salvar e carregar. O salvamento e o carregamento são feitos por alguma função do utilitário assíncrono (como http://winrtstoragehelper.codeplex.com/ ). Ao navegar do quadro 1 para o quadro 2 ou na outra direção, a carga assíncrona e as operações seguras são chamadas e aguardadas. Os manipuladores de eventos tornam-se assíncronos retornando nulos => eles não podem ser aguardados.
No entanto, a primeira operação de abertura de arquivo (digamos: dentro de uma função de salvamento) do utilitário também é assíncrona e, portanto, a primeira espera retorna o controle para a estrutura, que mais tarde chama o outro utilitário (carga) por meio do segundo manipulador de eventos. O carregamento agora tenta abrir o mesmo arquivo e, se o arquivo já estiver aberto para a operação de salvamento, falhará com uma exceção ACCESSDENIED.
Uma solução mínima para mim é proteger o acesso ao arquivo usando um AsyncLock e um.
Observe que o bloqueio dele basicamente bloqueia todas as operações de arquivo do utilitário com apenas um bloqueio, o que é desnecessariamente forte, mas funciona bem para o meu cenário.
Aqui está o meu projeto de teste: um aplicativo da Windows 8 App Store com algumas chamadas de teste para a versão original de http://winrtstoragehelper.codeplex.com/ e minha versão modificada que usa o AsyncLock de Stephen Toub http: //blogs.msdn. com / b / pfxteam / archive / 2012/02/12 / 10266988.aspx .
Também posso sugerir este link: http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
fonte
Stephen Taub implementou uma solução para esta pergunta, consulte Criando primitivas de coordenação assíncrona, parte 7: AsyncReaderWriterLock .
Stephen Taub é altamente considerado no setor, então qualquer coisa que ele escreva provavelmente será sólido.
Não reproduzirei o código que ele postou em seu blog, mas mostrarei como usá-lo:
Se você deseja um método inserido na estrutura .NET, use-o
SemaphoreSlim.WaitAsync
. Você não obterá um bloqueio de leitor / gravador, mas obterá uma implementação testada e testada.fonte
SemaphoreSlim.WaitAsync
na estrutura .NET. Tudo que esse código faz é adicionar um conceito de bloqueio de leitor / gravador.Hmm, parece feio, parece funcionar.
fonte
Eu tentei usar um Monitor (código abaixo) que parece funcionar, mas tem um GOTCHA ... quando você tem vários threads, ele fornecerá ... System.Threading.SynchronizationLockException O método de sincronização de objetos foi chamado a partir de um bloco de código não sincronizado.
Antes disso, eu estava simplesmente fazendo isso, mas estava em um controlador ASP.NET, resultando em um impasse.
public async Task<FooResponse> ModifyFooAsync() { lock(lockObject) { return SomeFunctionToModifyFooAsync.Result; } }
fonte