长时间运行同步方法的异步方法

时间:2014-04-02 19:07:59

标签: c# asynchronous task-parallel-library async-await c#-5.0

大家好,我对整个异步的东西都很陌生,如果你能给我一些建议会很好。我不确定我的方法是否合适。

假设我有一个正在读取数据的总线设备,如果电报完成,它会触发一个事件。现在我想检查每个电报的长度。如果length == expectation - >好的,如果没有尝试,直到可以或超时。但是它想要同时检查长度1,2和5。

更新 好的,我将我的示例更改为异步方法,但我仍然无法弄清楚这应该如何帮助解决我的问题?好吧,好的方面我不再拥有大部分时间都被阻止的线程,但这不是我的问题:(

所以我尝试以不同的方式解释。我想要一个异步方法,它监听总线并返回匹配定义长度的电报

async Task<byte[]> GetTelegramAsync(int length, Timespan timeout)

我想做这样的事情

Task<byte[]> t1 = GetTelegramAsync(1);
Task<byte[]> t2 = GetTelegramAsync(6);
Task<byte[]> t4 = GetTelegramAsync(4);
Task t4 = DoOtherStuffAsync();
DoStuff();

Task.WaitAll(AsyncRsp(t1), AsyncRsp(t2), AsyncRsp(t3), t4);

/* Output
Get telegram with length of 1
Get telegram with length of 6
Get telegram with length of 4 
Start doing other async stuff
Sync stuff done...
Telegram found 0x00 0x01 0x02 0x03 0x04 0x05
Async stuff done...
Telegram found 0xFF
Telegram with length 4 not found
*/

这是我的第一个BusDevice类。一个线程开始侦听总线,如果收到电报就会触发事件。

class BusDeviceThread
{
    private readonly Random _r = new Random();
    private Thread _t;

    public event EventHandler<TelegramReceivedArgs> TelegramReceived;               

    public void Connect()
    {
        _t = new Thread(FetchBusData)
        {
            Name = "FetchBusData",
            Priority = ThreadPriority.Normal
        };
        _t.Start();            
    }

    public void Close()
    {
        _t.Abort();
        _t.Join();
    }       

    private void FetchBusData()
    {
        while (true)
        {
            Thread.Sleep(_r.Next(100, 1000));

            var buffer = new byte[_r.Next(1, 10)];
            _r.NextBytes(buffer);
            OnTelegramReceived(new TelegramReceivedArgs(buffer));
        }
    }

    private void OnTelegramReceived(TelegramReceivedArgs e)
    {
        var handler = TelegramReceived;
        if (handler != null) handler(this, e);
    }        
}

这是使用异步等待的已更改的BusDevice类。

class BusDeviceAsync
{
    private readonly Random _r = new Random();

    public event EventHandler<TelegramReceivedArgs> TelegramReceived;

    public async Task Connect(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            var telegram = await FetchBusData();
            OnTelegramReceived(new TelegramReceivedArgs(telegram.ToArray()));
        }
    }

    private async Task<IEnumerable<byte>> FetchBusData()
    {
        await Task.Delay(_r.Next(100, 1000));

        var buffer = new byte[_r.Next(1, 10)];
        _r.NextBytes(buffer);

        return buffer;
    }

    private void OnTelegramReceived(TelegramReceivedArgs e)
    {
        var handler = TelegramReceived;
        if (handler != null) handler(this, e);
    }
}

就像我说它对我的问题没有帮助,

async Task<byte[]> GetTelegramAsync(int length, Timespan timeout)

实施保持不变或者我在这里错过了一个观点?

byte[] GetTelegram(int length, TimeSpan timeout)
{
   byte[] telegram = null;

   using (var resetEvent = new AutoResetEvent(false))
   {
       EventHandler<TelegramReceivedArgs> handler = (sender, e) =>
            {
                var t = e.Telegram;
                if (Check(t, length))
                {
                    telegram = t;
                    resetEvent.Set(); 
                }
            };
         _d.TelegramReceived += handler;
        resetEvent.WaitOne(timeout.Milliseconds);
        _d.TelegramReceived -= handler;
    }

    return telegram ?? new byte[0];
}

async Task<byte[]> GetTelegramAsync(int length, TimeSpan timeout)
{
    return await Task.Run(() => GetTelegram(length, timeout));
}

1 个答案:

答案 0 :(得分:0)

  

我更新了我的例子,但我无法弄清楚它的区别   我的问题。好吧,我当然已经修复了被阻止的线程“问题”。

这不完全是我的意思,你仍然在使用数据的拉模型(现在有Task.Delay的帮助),而不是推模型(通知是从公交车司机异步进来的,如图所示here)。

无论如何,我认为以下实现可能是您正在寻找的。请注意,除了异步I / O总线读取模拟之外,它根本不显式使用线程。将实际设备APM API替换为readBus

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication
{
    class Program
    {
        public class TelegramEventArg: EventArgs
        {
            public byte[] Data { get; set; }
        }

        public EventHandler<TelegramEventArg> TelegramEvent = delegate { };

        async Task<byte[]> ReadTelegramAsync(int size, CancellationToken token)
        {
            var tcs = new TaskCompletionSource<byte[]>();
            EventHandler<TelegramEventArg> handler = null;
            bool subscribed = false;

            handler = (s, e) => 
            {
                if (e.Data.Length == size)
                {
                    this.TelegramEvent -= handler;
                    subscribed = false;
                    tcs.TrySetResult(e.Data);
                }
            };

            this.TelegramEvent += handler;
            try
            {
                subscribed = true;
                using (token.Register(() => tcs.TrySetCanceled()))
                {
                    await tcs.Task.ConfigureAwait(false);
                    return tcs.Task.Result;
                }
            }
            finally
            {
                if (subscribed)
                    this.TelegramEvent -= handler;
            }
        }

        async Task ReadBusAsync(CancellationToken token)
        {
            while (true)
            {
                // get data from the bus
                var data = await Task.Factory.FromAsync(
                    (asyncCallback, asyncState) => 
                        readBus.BeginInvoke(asyncCallback, asyncState),
                    (asyncResult) => 
                        readBus.EndInvoke(asyncResult), 
                    state: null).ConfigureAwait(false);

                token.ThrowIfCancellationRequested();
                this.TelegramEvent(this, new TelegramEventArg { Data = data });
            }
        }

        // simulate the async bus driver with BeginXXX/EndXXX APM API
        static readonly Func<byte[]> readBus = () =>
        {
            var random = new Random(Environment.TickCount);
            Thread.Sleep(random.Next(1, 500));
            var data = new byte[random.Next(1, 5)];
            Console.WriteLine("A bus message of {0} bytes", data.Length);
            return data;
        };

        static void Main(string[] args)
        {
            try
            {
                var program = new Program();
                var cts = new CancellationTokenSource(Timeout.Infinite); // cancel in 10s

                var task1 = program.ReadTelegramAsync(1, cts.Token);
                var task2 = program.ReadTelegramAsync(2, cts.Token);
                var task3 = program.ReadTelegramAsync(3, cts.Token);

                var busTask = program.ReadBusAsync(cts.Token);

                Task.WaitAll(task1, task2, task3);
                Console.WriteLine("All telegrams received");
                cts.Cancel(); // stop ReadBusAsync
            }
            catch (Exception ex)
            {
                while (ex is AggregateException)
                    ex = ex.InnerException;
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();
        }
    }
}

此外,此方案似乎是使用Reactive Extensions (Rx)实施的理想候选人。随着时间的推移,我将展示如何做到这一点。