使用Task.Run同步调用异步方法最终导致死锁

时间:2016-11-14 04:37:56

标签: c# asynchronous async-await

我认为使用Task.Run调用异步方法不会导致任何死锁。但确实如此:

async Task<string> GetIdAsync()
{
    return Task.Run( () => IncInt64Async( "id", 1L ) ).Result.ToString(); // Deadlock
}

async Task<Int64> IncInt64Async( string key, Int64 inc );

我知道我应该等待。所以这个有效:

async Task<string> GetIdAsync()
{
    return await IncInt64Async( "id", 1L ); // of course, no deadlock.
}

但我只是想了解为什么第一次尝试导致死锁。在我对异步的理解中,只有当异步方法捕获同步上下文并尝试在上下文不能让它返回上下文时才会发生死锁。

此外,在Task.Run()内部调用GetIdAsync(),因此无论如何它都在运行时没有同步上下文:

Task.Run( () => {
    some code...
    id = getIdAsync();
    some code... 
}
);

所以我疯了,尝试了我能想到的所有变化,但仍然所有尝试都会导致死锁:

Task.Run( () => IncInt64Async( "id", 1L ).ConfigureAwait(false).GetAwaiter().GetResult() ).ConfigureAwait(false).GetAwaiter().GetResult();
IncInt64Async( "id", 1L ).ConfigureAwait(false).GetAwaiter().GetResult();
Task.Run( () => IncInt64Async( "id", 1L ) ).Result;
Task.Run( () => IncInt64Async( "id", 1L ) ).ConfigureAwait(false).GetAwaiter().GetResult();
Task.Run( async () => await IncInt64Async( "id", inc ) ).ConfigureAwait(false).GetAwaiter().GetResult();
Task.Run( () => { var task = IncInt64Async( "id", inc ); task.Wait(); return task.Result; } ).Result;

BTW,在IncInt64Async的实现中,它使用了几个await而没有ConfigureAwait(false)。

可能是什么原因?

IncInt64Async()的实现:

async Task<Int64> IncInt64Async( string key, Int64 value )
{
    Task task = null;
    byte error = 1;
    byte[] bytes = null;
    lock ( c.WriteLock )
    {
        c.Write( key, value );

        _readTask = task = _readTask.ContinueWith( async o =>
        {
            if ( ( error = await c.ReadByteAsync() ) == 0 ) // **BLOCKS HERE**
            {
                bytes = new byte[ 8 ];
                await c.ReadAsync( bytes, 0, 8 );
            }
            NumWaits--;
        } ).Unwrap();
    }

    if ( task != null )
        await task.AnyContext();

    if ( error != 0 )
        throw new InvalidOperationException();

    return BitConverter.ToInt64( bytes, 0 );
}

'c'是TcpHandler的一个实例。

它基本上是通过tcp写的,并从tcp读取。

我正在使用ContinueWith()来对齐读取。 我已经尝试将TaskScheduler.Default传递给ContinueWith()。

0 个答案:

没有答案