重新想象 Windows 8 Store Apps (68)_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > 重新想象 Windows 8 Store Apps (68)

重新想象 Windows 8 Store Apps (68)

 2013/10/28 9:58:06  后台任务: 控制通道(ControlChannel)  博客园  我要评论(0)
  • 摘要:[源码下载]重新想象Windows8StoreApps(68)-后台任务:控制通道(ControlChannel)作者:webabcd介绍重新想象Windows8StoreApps之后台任务控制通道(ControlChannel)示例1、客户端与服务端做ControlChannel通信的关键代码ControlChannelHelper/AppContext.cs/**本例通过全局静态变量来实现app与task的信息共享,以便后台任务可以获取到app中的相关信息**注:*也可以通过Windows
  • 标签:Windows APP

[源码下载]


重新想象 Windows 8 Store Apps (68) - 后台任务: 控制通道(ControlChannel)



作者:webabcd


介绍
重新想象 Windows 8 Store Apps 之 后台任务

  • 控制通道(ControlChannel)



示例
1、客户端与服务端做 ControlChannel 通信的关键代码
ControlChannelHelper/AppContext.cs

/*
 * 本例通过全局静态变量来实现 app 与 task 的信息共享,以便后台任务可以获取到 app 中的相关信息
 * 
 * 注:
 * 也可以通过 Windows.ApplicationModel.Core.CoreApplication.Properties 保存数据,以实现 app 与 task 的信息共享
 */

using System.Collections.Concurrent;
using Windows.Networking.Sockets;

namespace ControlChannelHelper
{
    public class AppContext
    {
        /// <summary>
        /// 从 ControlChannel 接收到的数据
        /// </summary>
        public static ConcurrentQueue<string> MessageQueue = new ConcurrentQueue<string>();

        /// <summary>
        /// 客户端 socket
        /// </summary>
        public static StreamSocket ClientSocket;
    }
}

ControlChannelHelper/SocketControlChannel.cs

/*
 * 实现一个 socket tcp 通信的 ControlChannel,client 将在此 ControlChannel 中实时接收数据
 * 
 * 注:
 * win8 client 和 socket server 不能部署在同一台机器上,否则会抛出异常:{参考的对象类型不支持尝试的操作。 (异常来自 HRESULT:0x8007273D)}
 */

using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Foundation;
using Windows.Networking;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace ControlChannelHelper
{
    public class SocketControlChannel : IDisposable
    {
        // ControlChannel
        public ControlChannelTrigger Channel { get; set; }

        // 客户端 socket
        private StreamSocket _socket;
        // 用于发送数据
        private DataWriter _dataWriter;
        // 用于接收数据
        private DataReader _dataReader;

        // 向服务端发送心跳的间隔时间,单位为分钟,最小 15 分钟
        private uint _serverKeepAliveInterval = 15;
        // ControlChannel 的标识
        private string _channelId = "myControlChannel";

        public SocketControlChannel()
        {

        }

        public async Task<string> CreateChannel()
        {
            Dispose();

            try
            {
                // 实例化一个 ControlChannel
                Channel = new ControlChannelTrigger(_channelId, _serverKeepAliveInterval, ControlChannelTriggerResourceType.RequestHardwareSlot);
            }
            catch (Exception ex)
            {
                Dispose();
                return "控制通道创建失败:" + ex.ToString();
            }

            // 注册用于向服务端 socket 发送心跳的后台任务,需要在 manifest 中做相关配置
            var keepAliveBuilder = new BackgroundTaskBuilder();
            keepAliveBuilder.Name = "myControlChannelKeepAlive";
            // 注:如果走的是 WebSocket 协议,则系统已经为其内置了发送心跳的逻辑,此处直接指定为 Windows.Networking.Sockets.WebSocketKeepAlive 即可
            keepAliveBuilder.TaskEntryPoint = "BackgroundTaskLib.ControlChannelKeepAlive";
            keepAliveBuilder.SetTrigger(Channel.KeepAliveTrigger); // 到了发送心跳的间隔时间时则触发,本例是 15 分钟
            keepAliveBuilder.Register();

            // 注册用于向用户显示通知的后台任务,需要在 manifest 中做相关配置
            var pushNotifyBuilder = new BackgroundTaskBuilder();
            pushNotifyBuilder.Name = "myControlChannelPushNotification";
            pushNotifyBuilder.TaskEntryPoint = "BackgroundTaskLib.ControlChannelPushNotification";
            pushNotifyBuilder.SetTrigger(Channel.PushNotificationTrigger); // 在 ControlChannel 中收到了推送过来的数据时则触发
            pushNotifyBuilder.Register();

            try
            {
                _socket = new StreamSocket();
                AppContext.ClientSocket = _socket;

                // 在 ControlChannel 中通过指定的 StreamSocket 通信
                Channel.UsingTransport(_socket);

                // client socket 连接 server socket
                await _socket.ConnectAsync(new HostName("192.168.6.204"), "3366");

                // 开始等待 ControlChannel 中推送过来的数据,如果 win8 client 和 socket server 部署在同一台机器上,则此处会抛出异常
                ControlChannelTriggerStatus status = Channel.WaitForPushEnabled();

                if (status != ControlChannelTriggerStatus.HardwareSlotAllocated && status != ControlChannelTriggerStatus.SoftwareSlotAllocated)
                    return "控制通道创建失败:" + status.ToString();

                // 发送数据到服务端
                _dataWriter = new DataWriter(_socket.OutputStream);
                string message = "hello " + DateTime.Now.ToString("hh:mm:ss") + "^";
                _dataWriter.WriteString(message);
                await _dataWriter.StoreAsync();

                // 接收数据
                ReceiveData();
            }
            catch (Exception ex)
            {
                Dispose();
                return "控制通道创建失败:" + ex.ToString();
            }

            return "ok";
        }

        // 开始接收此次数据
        private void ReceiveData()
        {
            uint maxBufferLength = 256;

            try
            {
                var buffer = new Windows.Storage.Streams.Buffer(maxBufferLength);
                var asyncOperation = _socket.InputStream.ReadAsync(buffer, maxBufferLength, InputStreamOptions.Partial);
                asyncOperation.Completed = (IAsyncOperationWithProgress<IBuffer, uint> asyncInfo, AsyncStatus asyncStatus) =>
                {
                    switch (asyncStatus)
                    {
                        case AsyncStatus.Completed:
                        case AsyncStatus.Error:
                            try
                            {
                                IBuffer bufferRead = asyncInfo.GetResults();
                                uint bytesRead = bufferRead.Length;
                                _dataReader = DataReader.FromBuffer(bufferRead);

                                // 此次数据接收完毕
                                ReceiveCompleted(bytesRead);
                            }
                            catch (Exception ex)
                            {
                                AppContext.MessageQueue.Enqueue(ex.ToString());
                            }
                            break;
                        case AsyncStatus.Canceled:
                            AppContext.MessageQueue.Enqueue("接收数据时被取消了");
                            break;
                    }
                };
            }
            catch (Exception ex)
            {
                AppContext.MessageQueue.Enqueue(ex.ToString());
            }
        }

        public void ReceiveCompleted(uint bytesRead)
        {
            // 获取此次接收到的数据
            uint bufferLength = _dataReader.UnconsumedBufferLength;
            string message = _dataReader.ReadString(bufferLength);

            // 将接收到的数据放到内存中,由 PushNotificationTrigger 触发的后台任进行处理(当然也可以在此处处理)
            AppContext.MessageQueue.Enqueue(message);

            // 开始接收下一次数据
            ReceiveData();
        }

        // 释放资源
        public void Dispose()
        {
            lock (this)
            {
                if (_dataWriter != null)
                {
                    try
                    {
                        _dataWriter.DetachStream();
                        _dataWriter = null;
                    }
                    catch (Exception ex)
                    {

                    }
                }

                if (_dataReader != null)
                {
                    try
                    {
                        _dataReader.DetachStream();
                        _dataReader = null;
                    }
                    catch (Exception exp)
                    {

                    }
                }

                if (_socket != null)
                {
                    _socket.Dispose();
                    _socket = null;
                }

                if (Channel != null)
                {
                    Channel.Dispose();
                    Channel = null;
                }
            }
        }
    }
}


2、服务端
BackgroundTaskLib/ControlChannelKeepAlive.cs

/*
 * 用于向服务端 socket 发送心跳的后台任务
 * 
 * 注:
 * 如果走的是 WebSocket 协议,则系统已经为其内置了发送心跳的逻辑
 * 只需要将 BackgroundTaskBuilder.TaskEntryPoint 设置为 Windows.Networking.Sockets.WebSocketKeepAlive 即可,而不需要再自定义此后台任务
 */

using ControlChannelHelper;
using System;
using Windows.ApplicationModel.Background;
using Windows.Networking.Sockets;
using Windows.Storage.Streams;

namespace BackgroundTaskLib
{
    public sealed class ControlChannelKeepAlive : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            if (taskInstance == null)
                return;

            // 获取 ControlChannel
            var channelEventArgs = taskInstance.TriggerDetails as IControlChannelTriggerEventDetails;
            ControlChannelTrigger channel = channelEventArgs.ControlChannelTrigger;

            if (channel == null)
                return;

            string channelId = channel.ControlChannelTriggerId;

            // 发送心跳
            SendData();
        }

        private async void SendData()
        {
            // 发送心跳到 server socket
            DataWriter dataWriter = new DataWriter(AppContext.ClientSocket.OutputStream);
            string message = "hello " + DateTime.Now.ToString("hh:mm:ss") + "^";
            dataWriter.WriteString(message);
            await dataWriter.StoreAsync();
        }
    }
}

BackgroundTaskLib/ControlChannelPushNotification.cs

/*
 * 用于向用户显示通知的后台任务,需要在 manifest 中做相关配置
 */

using ControlChannelHelper;
using NotificationsExtensions.ToastContent;
using System;
using Windows.ApplicationModel.Background;
using Windows.Networking.Sockets;
using Windows.UI.Notifications;

namespace BackgroundTaskLib
{
    public sealed class ControlChannelPushNotification : IBackgroundTask
    {
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            if (taskInstance == null)
                return;

            // 获取 ControlChannel
            var channelEventArgs = taskInstance.TriggerDetails as IControlChannelTriggerEventDetails;
            ControlChannelTrigger channel = channelEventArgs.ControlChannelTrigger;

            if (channel == null)
                return;

            string channelId = channel.ControlChannelTriggerId;

            try
            {
                string messageReceived;

                // 将从 ControlChannel 中接收到的信息,以 toast 的形式弹出
                while (AppContext.MessageQueue.Count > 0)
                {
                    bool result = AppContext.MessageQueue.TryDequeue(out messageReceived);
                    if (result)
                    {
                        IToastText01 templateContent = ToastContentFactory.CreateToastText01();
                        templateContent.TextBodyWrap.Text = messageReceived;
                        templateContent.Duration = ToastDuration.Short; 
                        IToastNotificationContent toastContent = templateContent;
                        ToastNotification toast = toastContent.CreateNotification();

                        ToastNotifier toastNotifier = ToastNotificationManager.CreateToastNotifier();
                        toastNotifier.Show(toast);
                    }
                }
            }
            catch (Exception ex)
            {

            }
        }
    }
}


3、客户端
BackgroundTask/ControlChannel.xaml

<Page
    x:Class="XamlDemo.BackgroundTask.ControlChannel"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:XamlDemo.BackgroundTask"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <Grid Background="Transparent">
        <StackPanel Margin="120 0 0 0">
            
            <TextBlock Name="lblMsg" FontSize="14.667" />
            
            <Button Name="btnCreateChannel" Content="创建一个 ControlChannel" Margin="0 10 0 0" Click="btnCreateChannel_Click" />
            
        </StackPanel>
    </Grid>
</Page>

BackgroundTask/ControlChannel.xaml.cs

/*
 * 演示如何创建一个基于 socket tcp 通信的 ControlChannel,client 将在此 ControlChannel 中实时接收数据
 * 
 * 注:
 * 不能在模拟器中运行
 * RTC - Real Time Communication 实时通信
 * win8 client 和 socket server 不能部署在同一台机器上,否则会抛出异常:{参考的对象类型不支持尝试的操作。 (异常来自 HRESULT:0x8007273D)}
 */

using System;
using ControlChannelHelper;
using Windows.ApplicationModel.Background;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Popups;

namespace XamlDemo.BackgroundTask
{
    public sealed partial class ControlChannel : Page
    {
        public ControlChannel()
        {
            this.InitializeComponent();
        }

        private async void btnCreateChannel_Click(object sender, RoutedEventArgs e)
        {
            // 如果 app 在锁屏上,则可以通过 ControlChannelTrigger 触发指定的后台任务
            BackgroundAccessStatus status = BackgroundExecutionManager.GetAccessStatus();
            if (status == BackgroundAccessStatus.Unspecified)
            {
                status = await BackgroundExecutionManager.RequestAccessAsync();
            }
            if (status == BackgroundAccessStatus.Denied)
            {
                await new MessageDialog("请先将此 app 添加到锁屏").ShowAsync();
                return;
            }

            // 创建一个基于 socket tcp 通信的 ControlChannel,相关代码参见:ControlChannelHelper 项目
            SocketControlChannel channel = new SocketControlChannel();
            string result = await channel.CreateChannel();

            lblMsg.Text = result;
        }
    }
}



OK
[源码下载]

发表评论
用户名: 匿名