在 Dispose 上同步事件处理程序

问题描述 投票:0回答:1

注意:虽然这个问题包含来自 Selenium WebDriver 和 Chrome DevTools 的详细信息,但我相信这个问题在更一般的线程同步上下文中也相关。

我有代码可以捕获来自浏览器的所有网络请求并对其执行某些操作(日志记录是一个典型的示例,但有时我会用它执行其他更复杂的操作)。为此,我为

Network.RequestPaused
事件创建了一个事件处理程序。当处理程序完成所需的操作时,它会调用
Network.ContinueRequestWithoutModification
继续从浏览器发送请求。在我的实际使用中,我有时也会调用
Network.ContinueRequestWithResponse
,修改请求等等,但在下面的示例中,为了简单起见,我只调用
Network.ContinueRequestWithoutModification
。 请注意,
Network.RequestPaused
事件是在与运行测试的线程不同的线程上触发的。

一切似乎都工作正常,但偶尔我会在事件处理程序调用

Network.ContinueRequestWithoutModification
时收到以下异常:

System.Net.WebSockets.WebSocketException : The WebSocket is in an invalid state ('CloseSent') for this operation. Valid states are: 'Open, CloseReceived'

当我调查这个问题时,我意识到我在

Dispose
DevToolsSession
对象(也拥有
Network
对象)的同时得到了这个异常。发生异常是有道理的,因为处理程序是在不同的线程上执行的,并且我可能在执行
Network.ContinueRequestWithoutModification
期间甚至稍后一点调用
DevToolsSession.Dispose
。我设法通过以下测试重现问题(尽管仅使用 [Repeat(20)])

    [Test, Repeat(20)]
    public void EventHandlersCanRunAfterDevToolsIsDisposed()
    {
        using var driver = new ChromeDriver();
        var caughtExceptions = new List<Exception>();

        using (var devToolsSession = driver.GetDevToolsSession(new DevToolsOptions { ProtocolVersion = 127 }))
        {
            var networkAdapter = new NetworkAdapter(devToolsSession);
            var fetch = new FetchAdapter(devToolsSession);
            var network = new V127Network(networkAdapter, fetch);

            var enableCommandSettings = new EnableCommandSettings
            {
                Patterns = new RequestPattern[]
                {
                    new()
                    {
                        RequestStage = RequestStage.Request,
                        UrlPattern = "*"
                    }
                }
            };
            fetch.Enable(enableCommandSettings);

            network.RequestPaused += async (_, args) =>
            {
                try
                {
                    // Do some stuff...
                    await network.ContinueRequestWithoutModification(args.RequestData);
                }
                catch (Exception ex)
                {
                    caughtExceptions.Add(ex);
                }
            };

            driver.Url = "https://www.google.com";
        } // Disposing DevToolsSession

        if (caughtExceptions.Count != 0)
            throw new AggregateException(caughtExceptions);
    }

注意:即使我在处置之前取消注册事件处理程序

DevToolsSession
我仍然可能会遇到异常,因为处理程序可能已经启动了。

如何避免这些异常情况?试图思考各种线程同步机制,但没有找到任何简单且健壮的解决方案。

c# multithreading selenium-webdriver events chrome-devtools-protocol
1个回答
0
投票

我不知道你的简单和稳健的标准,但这对我来说似乎是一个解决方案:

public class Network {
    public event EventHandler RequestPaused;

    public void FireEvent() {
        RequestPaused.Invoke(null, null);
    }

}

public class Session : IDisposable {
    public void Dispose() {
        Console.WriteLine("Disposing");
    }
}

var session = new Session();
var network = new Network();

int count = 0;
const int stopValue = -1;

network.RequestPaused += async (sender, args) => {
    if (Volatile.Read(ref count) == stopValue) {
        Console.WriteLine("Disposing called");
        return;
    }

    Interlocked.Increment(ref count);

    await Task.Delay(1);

    Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
    Interlocked.Decrement(ref count);
};

for (int i = 1; i <= 10; i++) {
    var captured = i;

    Task.Run(() => {
        Thread.Sleep(captured * 250);
        network.FireEvent();
    });
}

Thread.Sleep(500);

// wait til we dont have active operations, i.e. count is 0
// then set the stopValue so no new operations are started
while (0 != Interlocked.CompareExchange(ref count, stopValue, 0)) {
    Console.WriteLine("operations in progress");
    Thread.Sleep(100); // or spin
}

session.Dispose();
© www.soinside.com 2019 - 2024. All rights reserved.