我在等待永遠不會回傳的一些異步 SpecFlow 單元測驗中遇到問題。我認為這與 SpecFlow SynchronizationContext 有關,但我不確定它是如何作業的。
我正在撰寫測驗的類定期在 IScheduler 上運行代碼。此代碼等待在外部事件上完成的任務。在單元測驗中,IScheduler 是一個 TestScheduler,外部事件 Task 由一些存根代碼模擬。
我能夠讓它作業的唯一方法是確保從同步SpecFlow 測驗步驟中呼叫等待和任務完成。
- 如果從異步測驗步驟呼叫 await,則 await 永遠不會回傳。
- 如果從同步測驗步驟呼叫等待并從異步測驗步驟呼叫任務完成,那么等待將回傳,但在作業執行緒上,因此不受單元測驗的控制,所以我必須延遲以確保它發生。
- 如果從同步測驗步驟呼叫 await 和任務完成,則 await 將在與單元測驗相同的執行緒上同步回傳。
我的假設是,當從異步測驗步驟呼叫 await 時,它會SynchronizationContext.Current被捕獲,并且當任務完成時,它會嘗試回傳該 SynchronizationContext,但它不再有效,因為它是前一個測驗步驟。當從同步測驗步驟呼叫時,我注意到它SynchronizationContext.Current是空的,我認為這就是為什么等待與任務完成同步回傳(這是我想要測驗的)
我可以對等待和/或任務完成(兩者都在測驗存根代碼中)做些什么來確保等待忽略SynchronizationContext.Current并始終在測驗中同步回傳?
我正在使用 SpecFlow 3.9.58
我能夠使用以下代碼重現此問題:
Feature: Await
Scenario: Wait Async, Set Async
Given we await asynchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Sync
Given we await synchronously
When we set synchronously
Then await should have completed
Scenario: Wait Async, Set Sync
Given we await asynchronously
When we set synchronously
Then await should have completed
Scenario: Wait Sync, Set Async
Given we await synchronously
When we set asynchronously
Then await should have completed
Scenario: Wait Sync, Set Async, Delay
Given we await synchronously
When we set asynchronously
And we wait a bit
Then await should have completed
using Microsoft.Reactive.Testing;
using NUnit.Framework;
using System.Reactive.Concurrency;
using System.Threading;
using System.Threading.Tasks;
using TechTalk.SpecFlow;
namespace Test.SystemTests
{
[Binding]
public sealed class AwaitSteps
{
private TestScheduler _scheduler = new TestScheduler();
private TaskCompletionSource _tcs;
private bool _waiting;
// The previous Wait() implementation before Zer0's answer
//private async Task Wait()
//{
// _tcs = new TaskCompletionSource();
// _waiting = true;
// await _tcs.Task;
// _waiting = false;
//}
// Updated Wait()/NestedWait() following Zer0's answer
private async Task NestedWait()
{
// Simulate the code in the stub class (can change this)
_tcs = new TaskCompletionSource();
await _tcs.Task.ConfigureAwait(false);
}
private async Task Wait()
{
// Simulate the code in the class under test (prefer not to change this)
_waiting = true;
await NestedWait();
_waiting = false;
}
private void WaitOnScheduler()
{
_scheduler.Schedule(async () => await Wait());
_scheduler.AdvanceBy(1);
}
private void Set() => _tcs.TrySetResult();
[Given(@"we await asynchronously")]
public async Task GivenWeAwaitAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
WaitOnScheduler();
}
[Given(@"we await synchronously")]
public void GivenWeAwaitSynchronously() => WaitOnScheduler();
[When(@"we set asynchronously")]
public async Task WhenWeSetAsynchronously()
{
await Task.CompletedTask; // Just to make the step async
Set();
}
[When(@"we set synchronously")]
public void WhenWeSetSynchronously() => Set();
[When(@"we wait a bit")]
public async Task WhenWeWaitABit() => await Task.Delay(100);
[Then(@"await should have completed")]
public void ThenAwaitShouldHaveCompleted() => Assert.AreEqual(false, _waiting);
}
}

uj5u.com熱心網友回復:
我的假設是,當從異步測驗步驟呼叫 await 時,會捕獲 SynchronizationContext.Current,并且當任務完成時,它會嘗試回傳該 SynchronizationContext
這可以用 來改變ConfigureAwait(false)。我一個人做出了改變:
private async Task Wait()
{
_tcs = new TaskCompletionSource();
_waiting = true;
await _tcs.Task.ConfigureAwait(false);
_waiting = false;
}
并得到這些結果,使用你的代碼逐字減去那個變化。

關于必須一直打電話ConfigureAwait(false),有幾種解決方法。這里有關于所有這些的有用檔案,包括詳細資訊和避免捕獲的方法,例如簡單的Task.Run呼叫。
在這種情況下,最簡單的方法是在設定場景時執行此操作:
SynchronizationContext.SetSynchronizationContext(null);
這完全消除了對捕獲的需要,ConfigureAwait因為沒有任何東西可以捕獲。
還有其他選擇。如果這不能回答您的問題,請參閱鏈接的文章或評論。
AFAIK,任何地方都沒有公開的屬性來更改默認行為,因此無論如何都需要一些代碼。
也就是說ConfigureAwait(false),當其他編碼人員閱讀它時,您正在做什么非常明確。因此,如果您想要不同的解決方案,我會記住這一點。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/468140.html
