在不堵塞线程的情况下异步检查值?

时间:2016-03-17 16:34:44

标签: c# asynchronous

我的应用程序中有一个模式,我想发送一些命令或启动一些IO工作并等待它完成,或者有一些机制来知道它是否已经完成。

为此,我计划使用async / await模式。我假设大多数人已经到了编码的地步,他们达到了这种模式。

while(something)
    DoNothing();

DoNothing()通常会占用一些CPU时间或完全停止程序。我认为解决这个问题的正确方法是使用async / await模式。

就我而言,我提出了以下简单方法

public override async Task<Boolean> PerformProcessingAsync()
{
    StartSomeIOProcessing();
    while (TheIOProcessingResult == null)
        await Task.Yield();
    return true;
}

启动某些IO处理,然后等待实例化结果。同时,虽然它调用Task.Yield返回到调用上下文,然后可以继续工作,并将此方法的延续(下一个while循环迭代)放到调用堆栈上。

这是正确的解释,这是解决上述情况的正确方法吗?

编辑:在我面临的更具体的情况下......我还维护另一个执行IO工作的库,基本上是对SerialPort个对象的读写。该库通过对特定端口进行处理的ConcurrentQueue读取或写入来工作。 Task“位于此队列的末尾,并随着工作消耗。

大多数阅读工作只是查询某些值,解析它,并触发由UI监听的事件NewData(double myNewData)。在UI中,保存了SerialPort数据的状态表示。在状态表示中,处理NewData事件,该事件更新它对应的任何值。

写入以相同的方式完成,但是在完成时没有触发事件,只是写入端口。判断它是否成功的唯一方法是等待读取更新状态。 (遗憾的是,由于硬件的作用,没有更好的方法可以做到这一点。)

我当前的应用程序使用此库来执行其IO工作。读取会定期发送到库以保持UI具有来自端口的新值...当用户单击按钮时,写入将被发送到库以从硬件命令。

我处于这样的情况,我想确保写入已经发生以编程方式。我能想到的唯一方法是将写入发送到库中,并等待读取更新数据写入效果。

因此循环。

3 个答案:

答案 0 :(得分:1)

  

我处于这样的情况,我想确保写入已经以编程方式发生。我能想到的唯一方法是将写入发送到库中,并等待读取更新数据写入效果。

是的,这可能是唯一的方法,但有更好的方法,而不是旋转等待查看读取更新是否发生。

这是一个处理处理的示例方法,就像我们在the chat中所说的那样。总而言之,您的队列为IDataRequest,在这些请求中,他们持有TaskCompletionSource<T>代表发送数据的完成情况。一旦您向设备发出请求并获得响应,您就可以设置完成源的结果。我将它与您现有的基于事件的implmentation结合起来,但老实说,我会放弃事件,并等待调用者在等待RequestTemp()的结果后对UI进行更新。

public interface IDataRequest
{
    bool TrySetException(Exception ex);
}

public abstract class DataRequest<T> : IDataRequest
{
    public TaskCompletionSource<T> RequestTask { get; } = new TaskCompletionSource<T>();

    public bool TrySetException(Exception ex)
    {
        return RequestTask.TrySetException(ex);
    }
}

public class TempRequest : DataRequest<double>
{
}

public class RpmRequest : DataRequest<int>
{
}

public sealed class DeviceManager : IDisposable
{

    private readonly Task _workerThread;
    private readonly BlockingCollection<IDataRequest> _queue;
    private readonly SerialPort _serialPort;

    public DeviceManager()
    {
        _queue = new BlockingCollection<IDataRequest>();
        _workerThread = Task.Factory.StartNew(ProcessQueue, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default);
        _serialPort = //...
    }

    public event EventHandler<double> TempUpdate;
    public event EventHandler<int> RpmUpdate;

    public Task<double> RequestTemp()
    {
        var request = new TempRequest();
        _queue.Add(request);
        return request.RequestTask.Task;
    }

    public Task<int> RequestRpm()
    {
        var request = new RpmRequest();
        _queue.Add(request);
        return request.RequestTask.Task;
    }
    public void Dispose()
    {
        _queue.CompleteAdding();
        _workerThread.Wait();
    }

    private void ProcessQueue()
    {
        foreach (var dataRequest in _queue.GetConsumingEnumerable())
        {
            try
            {
                if (dataRequest is TempRequest)
                {
                    DoTempRequest((TempRequest)dataRequest);
                }
                else if (dataRequest is RpmRequest)
                {
                    DoRpmRequest((RpmRequest)dataRequest);
                }
                else
                {
                    throw new NotSupportedException($"A Request of type {dataRequest.GetType()} is not supported.");
                }
            }
            catch (Exception ex)
            {
                dataRequest.TrySetException(ex);
            }
        }
    }

    private void DoTempRequest(TempRequest dataRequest)
    {
        _serialPort.WriteLine("Temp ?");
        var line = _serialPort.ReadLine();
        double result;

        //I am deliberately using Parse instead of TryParse so responses that 
        //fail to parse will throw and get their exception propagated up via the 
        //catch in ProcessQueue().
        result = double.Parse(line);

        //Sends the info back to the caller saying it is done and what the result was.
        dataRequest.RequestTask.TrySetResult(result);

        //Raises the event so subscribers know the new value.
        OnTempUpdate(result);
    }

    private void DoRpmRequest(RpmRequest dataRequest)
    {
        _serialPort.WriteLine("RPM ?");
        var line = _serialPort.ReadLine();
        int result;
        result = int.Parse(line);

        dataRequest.RequestTask.TrySetResult(result);
        OnRpmUpdate(result);

    }

    private void OnTempUpdate(double result)
    {
        TempUpdate?.Invoke(this, result);
    }

    private void OnRpmUpdate(int result)
    {
        RpmUpdate?.Invoke(this, result);
    }
}

答案 1 :(得分:0)

我会将StartSomethingIOProcessing提取到一个返回结果的单独Task中。 Await就此而言,然后检查结果。

public async PerformProcessingAsync()
{
    var ioProcessingResult = await StartSomeIOProcessing();
    return (null != ioProcessingResult);
}

private Task<TheIOProcessingResultType> StartSomeIOProcessing()
{
    return Task.Run(()=>
    {
        return StartSomeIOProcessing();
    });
}

答案 2 :(得分:-1)

我相信你误解了async / await操作。 await关键字实际上用于等待异步操作完成,因此您不再需要实现while循环并检查它是否为空。

public override async Task<Boolean> PerformProcessingAsync()
{
    await Task.Run((() =>
    {
            StartSomeIOProcessing();
    });
    return true;
}