【问题标题】:SemaphoreSlim not working after a weekSemaphoreSlim 一周后不工作
【发布时间】:2018-07-12 20:33:55
【问题描述】:

更新:发现错误。我的代码按预期工作,但我的同事向我正在与之通信的设备添加了一个小代码。结果是它忽略了我的第二个命令,因为它正忙于做其他事情。 修复了它,现在它又可以工作了。

在一周的开始,我问了一个关于将串行端口的访问限制为一个实例的问题。

确实有人在那里帮助了我,并向我提到了SemaphoreSlim。 这在当时确实起到了作用,我认为就是这样。

现在几天后我终于解决了另一个主要问题,在测试它时我注意到信号量不再工作了。

我将代码回滚到之前的版本,在该版本中我刚刚实现了信号量并且它确实可以工作。 但即使是那个代码也不再工作了。

我的 Visual Studio、PC 或 Raspberry 中的任何内容都没有更改...

有人知道为什么理论上应该有效的东西现在不再有效了吗?

非常感谢这里的帮助:)

添加了我正在使用的代码的一个非常简短的版本。 通常我会处理进入 Int 或字符串数​​组的数据。 我还将进行 CRC16 验证,所有内容都将包含在 try/catch 块中,以便在异常发生时捕获它们。

但是,到目前为止,这就是我所需要的一切。 如果需要,我将尝试提供更详细的信息。让我知道。

当前行为: 第一个任务开始并且工作正常。 第二个任务没有启动,之后的每个新任务也没有启动。

预期行为: 开始第一个任务并完成它。 第一个任务完成后,加载第二个任务并完成它。 当我之后启动另一个任务时,它也应该运行它。

Mainpage.xaml

public MainPage()
    {
        this.InitializeComponent();

        InitPort();
    }

    private async void getData_Click(object sender, RoutedEventArgs e)
    {
        // Call the async operations.
        // I usually don't call the same operation twice at the same time but it's just a test here.
        // In normal usage the data contains different sizes.

        await getData();

        await getData();
    }

    private async Task getData()
    {
        ReadData readData = new ReadData();

        byte[] readOutput = await readData.ReadParaBlock();

        DisplayTextBox.Text = BitConverter.ToString(readOutput);            
    }

    public async void InitPort()
    {
        string success = await ReadWriteAdapter.Current.Init();
        DisplayTextBox.Text = success;
    }

ReadData.cs

public class ReadData
{
    private ReadBlock readBlock = new ReadBlock();

    public async Task<byte[]> ReadParaBlock()
    {
        // Load task into the semaphore
        await ReadWriteAdapter.Current.semaphore.WaitAsync();

        // start writing to device
        await readBlock.Write();

        // dropped check of recieved checksum because obsolete for test

        // start reading from device
        byte[] recievedArray = await readBlock.Read();

        // release the task from semaphore
        ReadWriteAdapter.Current.semaphore.Release();

        return recievedArray;            
    }
}

ReadBlock.cs

public class ReadBlock
{

    public async Task<uint> Write()
    {
        // Command sent to device to get data
        byte[] WriteArray = System.Text.Encoding.ASCII.GetBytes("getdata");            

        return await ReadWriteAdapter.Current.WriteAsync(WriteArray);
    }

    public async Task<byte[]> Read()
    {
        byte[] ListenOut = await ReadWriteAdapter.Current.Listen(100);

        // dropped data conversion because obsolete for test

        return ListenOut;
    }
}

ReadWriteAdapter.cs

public class ReadWriteAdapter
{
    public SemaphoreSlim semaphore { get; private set; }

    private static readonly Object lockObject = new object();
    private static ReadWriteAdapter instance;
    private DataWriter dataWriter = null;
    private DataReader dataReader = null;
    private SerialDevice serialPort = null;

    public static ReadWriteAdapter Current
    {
        get
        {
            if (instance == null)
            {
                lock (lockObject)
                {
                    if (instance == null)
                    {
                        instance = new ReadWriteAdapter();
                    }
                }
            }
            return instance;
        }
    }

    private ReadWriteAdapter()
    {
        this.semaphore = new SemaphoreSlim(1, 1);
    }

    // initialize the serial port and configure it
    public async Task<string> Init()
    {  
        string aqs = SerialDevice.GetDeviceSelector();

        DeviceInformationCollection devices = await DeviceInformation.FindAllAsync(aqs, null);

        if (devices.Any())
        {                
            string deviceId = devices[0].Id;

            serialPort = await SerialDevice.FromIdAsync(deviceId);

            serialPort.WriteTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.ReadTimeout = TimeSpan.FromMilliseconds(1000);
            serialPort.BaudRate = 19200;
            serialPort.Parity = SerialParity.None;
            serialPort.StopBits = SerialStopBitCount.One;
            serialPort.DataBits = 8;
            serialPort.Handshake = SerialHandshake.None;

            dataWriter = new DataWriter(serialPort.OutputStream);
            dataReader = new DataReader(serialPort.InputStream);                
        }

        return "found port";
    }

    // start to listen to the serial port
    public async Task<byte[]> Listen(uint BufferLength)
    {
        byte[] listen = new byte[BufferLength];

        dataReader = new DataReader(serialPort.InputStream);                    

        listen = await ReadAsync(BufferLength);

        if (dataReader != null)
        {
            dataReader.DetachStream();
            dataReader.Dispose();
            dataReader = null;
        }

        return listen;
    }

    // function to read and interpret the data from serial port
    private async Task<byte[]> ReadAsync(uint ReadBufferLength)
    {
        Task<uint> loadAsyncTask;
        byte[] returnArray = new byte[ReadBufferLength];

        dataReader.InputStreamOptions = InputStreamOptions.Partial;

        loadAsyncTask = dataReader.LoadAsync(ReadBufferLength).AsTask();

        uint bytesRead = await loadAsyncTask;

        if (bytesRead > 0)
        {
            dataReader.ReadBytes(returnArray);
        }            

        return returnArray;
    }

    // write the data using the serial port
    public async Task<uint> WriteAsync(byte[] data)
    {   
        dataWriter.WriteBytes(data);

        Task<uint> storeAsyncTask = dataWriter.StoreAsync().AsTask();

        return await storeAsyncTask;            
    }
}

【问题讨论】:

  • 这不是真正可以回答的。开始写一个尽可能小的minimal reproducible example
  • 我用我使用的一些短代码编辑了我的主帖。我把它精简为非常重要的东西,但它仍然不能那样工作:/
  • 您需要更具体地了解信号量是如何“不工作”的。你期待什么行为,你实际看到了什么?
  • 我希望它能限制我对串行端口的访问。这意味着当我一个接一个地启动 2 个任务时,它应该启动第一个任务,完成后它应该启动第二个任务。但对我来说,它只完成第一个任务而不是第二个任务,然后当我手动尝试启动另一个任务时,它甚至都没有启动。
  • WaitAsync 之后,您应该将代码包装在 try-finally 中,并在 finally 中使用 Release,以确保您始终释放标志,即使抛出异常也是如此。正如 MikeStrobel 在他的回答中指出的那样,问题很可能在于您没有这样做,并且从 IO 方法抛出异常,因此永远不会调用您的发布。

标签: c# uwp win-universal-app windows-10-universal


【解决方案1】:

这看起来应该可以工作,实际上,修改为使用MemoryStream 而不是串行端口的版本工作得很好。我无权访问您正在使用的 I/O API,所以我对它们应该如何使用一无所知。然而,信号量似乎做得很好。您将在下面找到示例的简化版本,其中包含用于串行或并行运行 GetData() 的按钮。

如果信号量有问题,只能是您没有从finally 内部调用Release()。否则,您对信号量的使用看起来不错。

要查看信号量是否正常工作,您只需将初始化程序更改为 new SemaphoreSlim(8, 8) 并查看线程之间的冲突。

ReadBlock.cs:

public class ReadBlock {
    private static int nextString;

    private static readonly string[] strings = {
        "ONE  ", "TWO  ", "THREE", "FOUR ",
        "FIVE ", "SIX  ", "SEVEN", "EIGHT"
    };

    public static async Task<byte[]> ReadParaBlock() {
        var id = Interlocked.Increment(ref nextString) - 1;
        var name = strings[id % strings.Length];

        try
        {
            await ReadWriteAdapter.Current.Semaphore.WaitAsync();
            Trace.WriteLine($"[{name.Trim()}] Entered Semaphore");
            await Write(Encoding.ASCII.GetBytes("TEST_" + name));
            return await Read();
        }
        finally {
            Trace.WriteLine($"[{name.Trim()}] Exiting Semaphore");
            ReadWriteAdapter.Current.Semaphore.Release();
        }
    }

    public static async Task<uint> Write(byte[] bytes) =>
        await ReadWriteAdapter.Current.WriteAsync(bytes);

    public static async Task<byte[]> Read() => await ReadWriteAdapter.Current.Listen(10);
}

ReadWriteAdapter.cs:

public class ReadWriteAdapter {
    private static ReadWriteAdapter instance;

    public static ReadWriteAdapter Current
        => LazyInitializer.EnsureInitialized(
            ref instance,
            () => new ReadWriteAdapter());

    private readonly MemoryStream stream = new MemoryStream();

    public SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);

    public async Task<string> Init()
        => await Task.FromResult("found port");

    public async Task<byte[]> Listen(uint bufferLength) 
        => await ReadAsync(bufferLength);

    private async Task<byte[]> ReadAsync(uint readBufferLength) {
        await Task.CompletedTask;
        var returnArray = new byte[readBufferLength];
        await stream.ReadAsync(returnArray, 0, returnArray.Length);
        return returnArray;
    }

    public async Task<uint> WriteAsync(byte[] data) {
        stream.SetLength(stream.Capacity);
        stream.Position = 0;
        await Task.Delay(1);
        await stream.WriteAsync(data, 0, data.Length);
        stream.SetLength(data.Length);
        stream.Position = 0;
        return (uint)data.Length;
    }
}

MainWindow.xaml.cs:

public partial class MainWindow {
    private class TextBoxTraceListener : TraceListener {
        private readonly TextBoxBase _textBox;

        public TextBoxTraceListener(TextBoxBase textBox) 
            => _textBox = textBox;

        public override void WriteLine(string message) 
            => Write(message + Environment.NewLine);

        public override void Write(string message) {
            _textBox.Dispatcher.BeginInvoke(
                DispatcherPriority.Normal,
                new Action(() => _textBox.AppendText(message)));
        }
    }

    public MainWindow() {
        TaskScheduler.UnobservedTaskException +=
            (s, e) => Trace.TraceError(e.ToString());
        InitializeComponent();
        Trace.Listeners.Add(new TextBoxTraceListener(textBox));
        InitPort();
    }

    private async void InitPort() {
        textBox.AppendText(await ReadWriteAdapter.Current.Init());
    }

    private async void OnGetDataInSerialClick(object sender, RoutedEventArgs e) {
        textBox.Clear();
        for (var i = 0; i < 8; i++) await GetData();
    }

    private async void OnGetDataInParallelClick(object sender, RoutedEventArgs e) {
        textBox.Clear();
        await Task.WhenAll(Enumerable.Range(0, 8).Select(i => GetData()));
    }

    private async Task GetData() {
        await Task.Delay(50).ConfigureAwait(false); // Get off the UI thread.
        Trace.WriteLine(Encoding.ASCII.GetString(await ReadBlock.ReadParaBlock()));
    }
}

MainWindow.xaml:

<Window x:Class="WpfTest2.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <DockPanel LastChildFill="True">
    <Button DockPanel.Dock="Bottom" Click="OnGetDataInSerialClick"
                Content="Load Data in Serial" />
    <Button DockPanel.Dock="Bottom" Click="OnGetDataInParallelClick"
                Content="Load Data in Parallel" />
    <TextBox x:Name="textBox" TextWrapping="Wrap" />
  </DockPanel>
</Window>

【讨论】:

  • 感谢您的验证。我还花了我的周末构建不同的解决方案,当我不使用我想发送到的设备时,它们都可以工作。我终于发现我的同事在发出完成信号后添加了一点在另一台设备上运行的代码......所以我们最终发现了错误:D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-18
  • 2018-02-09
  • 2018-02-16
  • 1970-01-01
  • 1970-01-01
  • 2023-04-06
相关资源
最近更新 更多