0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

英创信息技术嵌入式系统通讯线程的C#编程方法

英创信息技术 来源:英创信息技术 作者:英创信息技术 2020-01-15 11:25 次阅读

在使用英创ARM9系列嵌入式主板的COM口,CAN口,网口时,一般会使用到timer或线程来实现数据的接收。使用timer控件较为方便,通过InterVal值来设定调用间隔,但是灵活性不如线程。并且timer的Tick函数是并在主线程中,如果Tick函数中运算数据过于复杂,会导致主线程运行变慢,可能导致窗口卡死。使用C#中的线程类,可以非常方便的解决这个问题,线程卡死,不会影响到主线程的运算,就不会导致窗口卡死的状况发生。

本文将介绍如何使用C#来创建和关闭线程,并在此基础上,利用WinCE系统的消息机制实现通讯数据的实时收发,代替常规的定时查询方法,从而降低了CPU负载,使嵌入式设备的整体性能得以提高。

1、线程的应用实例

以下是一个简单的多线程代码:

using System;
using System.Threading;
namespace thread
{
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(excute);
t.Start();
while (true) Console.Write('1'); // 主线程循环输出1
}
static void excute()
{
while (true) Console.Write('2'); // 线程t循环输出2
}
}
}

输出例子(并不唯一):12121212121212121212121212121212121212121212121212...

2、线程的使用方法

首先需要添加thread类的引用

using System.Threading;

初始一个线程类,并设定它的执行函数,该函数可以是静态函数,也可以是别的类的成员函数

Thread t = new Thread(excute);

执行start,线程即启动并运行它的执行函数,函数运行完毕后,线程自动退出

t.Start();

3、线程的数据同步

观察以下代码:

using System;
using System.Threading;
namespace thread
{
class Program
{
static int i;
static void Main(string[] args)
{
Thread t = new Thread(excute);
t.Start();
excute();
}
static void excute()
{
for (i = 0; i < 5; i++)
{
Console.Write('{0}', i);
}
}
}
}

这个程序的输出无法确定,可能是:001234。

这是因为在一个线程在使用一个变量时,另外一个线程也可能同时在使用。如果希望一个线程在使用某个变量时,禁止其他线程的使用,就需要用到线程锁lock。

修改代码为:

using System;
using System.Threading;
namespace thread
{
class Program
{
static readonly object locker = new object();
static int i;
static void Main(string[] args)
{
Thread t = new Thread(excute);
t.Start();
excute();
}
static void excute()
{
lock (locker)
{
for (i = 0; i < 5; i++)
{
Console.Write('{0}', i);
}
}
}
}
}

程序输出:0123401234。

注意lock的使用,见MSDN的说明:

1、不要锁定this,即禁止lock(this)
2、不要锁定类型,如lock (typeof (MyType))
3、不要锁定字符串,如lock('myLock')
4、最佳做法是定义private或 private static对象来锁定

锁定本身是很快,一个锁在堵塞的情况,任务切换带来的开销很低,使用锁可以有效避免一些数据错误,提高程序稳定性。

4、线程的结束

使用abort可以提前释放被阻塞的线程,使用join可以等待线程的结束:

using System;
using System.Threading;
namespace thread
{
class Program
{
static int i;
static void Main(string[] args)
{
Thread t = new Thread(excute);
t.Start();
// t.Abort();
t.Join();
for (i = 6; i < 10; i++)
{
Console.Write('{0}', i);
}
}
static void excute()
{
for (i = 0; i < 5; i++)
{
Console.Write('{0}', i);
}
}
}
}

程序输出:0123456789。

如果取消Abort的注释,程序的输出可能是:6789。

在主线程中关闭副线程一般步骤为,终止副线程,再等待确认该线程退出,在主程序退出的时间同样需要执行检测副线程的关闭:

t.Abort();
t.Join();

5、带参数的线程

有时候希望在添加的线程中传入指定的参数。

最简单的办法是把类封装在类中,让线程的执行函数为类的成员函数,然后通过设置类的成员变量,执行函数访问成员变量这样的办法来实现指定执行函数参数的功能,例程如下:

using System;
using System.Threading;
namespace thread
{
class ThreadClass
{
public int x;
public void excute()
{
while (true) { Console.WriteLine('{0}', x); }
}
}
class Program
{
static void Main(string[] args)
{
ThreadClass TClass1 = new ThreadClass();
TClass1.x = 1;
ThreadClass TClass2 = new ThreadClass();
TClass2.x = 2;
Thread t1 = new Thread(TClass1.excute);
Thread t2 = new Thread(TClass2.excute);
t1.Start();
t2.Start();
}
}
}

还有一个另外的办法,使用ParameterizedThreadStart。

C#提供2种委托,ThreadStart和ParameterizedThreadStart,ParameterizedThreadStart允许传入一个参数Object,可以将所需参数打包后调用。

注意:wince使用的是.net精简库,不包含ParameterizedThreadStart,如果在wince下编程,请使用第一种方法。

6、线程的挂起和唤醒

当线程创建后,就将占用一定的CPU时间,可以使用Sleep函数让线程放弃一定时间片,进入休眠状态,在休眠状态下,线程将不再占用CPU时间,如:

Thread.Sleep(0); // 释放CPU时间片
Thread.Sleep(1000); // 休眠1000毫秒
Thread.Sleep(Timeout.Infinite); // 休眠直到被唤醒

使用线程的Interrupt方法可以强行唤醒休眠中的线程,注意,wince的.net精简库里,Thread类没有Interrupt方法,所以在嵌入式设备中开发时不要无限休眠线程,即Sleep(-1)。

7、线程的消息事件响应

有的时候需要在线程中轮询执行一个函数,如通信接口的接收函数。使用轮循的方式将非常浪费CPU时间。

private void BeginReceive() // 客户机状态下接收数据线程
{
while (!threadStop)
{
// 线程接收函数
}
}

在接收线程中加入适当休眠可以提高CPU效率,这里Sleep的x越大,CPU效率越高,但是可能造成数据处理的延时。

private void BeginReceive() // 客户机状态下接收数据线程
{
while (!threadStop)
{
// 线程接收函数
Thread.Sleep(x); // 轮询休眠
}
}

为了避免通讯数据接收的延时,线程还可采用等待数据接收事件的方式,线程在平时挂起,直到有数据接收的事件产生。
C#提供一套事件类,可以让线程进入等待状态,直到该事件到来,线程在等待时不会消耗CPU资源。

using System;
using System.Threading;
namespace thread
{
class Program
{
static AutoResetEvent evt;
static void Main(string[] args)
{
evt = new AutoResetEvent(false);
Thread t = new Thread(excute);
t.Start();
Thread.Sleep(10000);
evt.Set();
}
static void excute()
{
for ( ; ; )
{
evt.WaitOne();
Console.Write('event');
}
}
}
}

设定一个事件

static AutoResetEvent evt;

在线程等待该事件的时候挂起

evt.WaitOne();

直到该事件Set产生,线程才继续执行下面的代码:

evt.Set();

还可以设置等待的时间长短,当有事件产生,WaitOne函数立刻返回true,如果等待时间超过设置时间,WaitOne也会返回,返回值false。

using System;
using System.Threading;
namespace thread
{
class Program
{
static AutoResetEvent evt;
static void Main(string[] args)
{
evt = new AutoResetEvent(false);
Thread t = new Thread(excute);
t.Start();
evt.Set();
Thread.Sleep(1000);
evt.Set();
Thread.Sleep(10000);
evt.Set();
}
static void excute()
{
bool b;
for (; ; )
{
b = evt.WaitOne(1000, false);
Console.Write('{0}', b.ToString);
}
}
}
}

注意:WaitOne第二个参数一般设置为false。

但是使用C#的事件类可能有一定局限性,它需要在同一进程里,有一些情况无法满足需要。这时候可以使用系统的API函数来解决这个问题,参看以下代码。

using System;
using System.Threading;
using System.Runtime.InteropServices;
namespace thread
{
class Program
{
[DllImport('coredll.dll', EntryPoint = 'WaitForSingleObject')]
private static extern int WaitForSingleObject(int hHandle, int dwMilliseconds);
[DllImport('coredll.dll', EntryPoint = 'CreateEvent')]
private static extern int CreateEvent(int lpEventAttributes, int bManualReset, int bInitialState, int lpName);
[DllImport('coredll.dll', EntryPoint = 'EventModify')]
private static extern bool EventModify(int h, int i);
[DllImport('coredll.dll', EntryPoint = 'WaitForMultipleObjects')]
private static extern int WaitForMultipleObjects(uint nCount, int[] lpHandles, int bWaitAll, int dwMilliseconds);
[DllImport('coredll.dll', EntryPoint = 'CloseHandle')]
private static extern int CloseHandle(int hObject);
static int hEvt;
static void Main(string[] args)
{
hEvt = CreateEvent(0, 1, 0, 0); // CreateEvent(NULL,TRUE,FALSE,NULL)
EventModify(hEvt, 2); // ResetEvent(hEvt);
Thread t = new Thread(excute);
t.Start();
Thread.Sleep(1000);
EventModify(hEvt, 3); // SetEvent(hEvt);
Thread.Sleep(10000);
EventModify(hEvt, 3); // SetEvent(hEvt);
CloseHandle(hEvt);
}
static void excute()
{
int i;
for (; ; )
{
i = WaitForSingleObject(hEvt, -1); // 无限等待
// i = WaitForSingleObject(hEvt, 1000); // 等待1秒
EventModify(hEvt, 2); // ResetEvent(hEvt);
Console.Write('event');
}
}
}
}

这里使用了API函数,所以需要添加引用

using System.Runtime.InteropServices;

通过CreateEvent创建一个事件,并获得该事件句柄。这里参数一般使用(NULL,TRUE,FALSE,NULL),即(0, 1, 0, 0)

通过EventModify(hEvt, 2)将该事件的信号设置为无信号,该函数第一个参数为设置的事件句柄,第二个参数为2表示ResetEvent,第二个参数为3表示SetEvent。

hEvt = CreateEvent(0, 1, 0, 0); // CreateEvent(NULL,TRUE,FALSE,NULL)

EventModify(hEvt, 2); // ResetEvent(hEvt);

在线程中调用WaitForSingleObject函数等待事件,第一个参数为等待的事件句柄,第二个参数为等待的时间,如果为INFINITE即-1,表示一直等待,直到收到事件消息。该函数返回0表示接收到消息,返回0x102表示未接收到消息等待超时

i = WaitForSingleObject(hEvt, -1); // 无限等待

当主线程执行SetEvent即EventModify(hEvt, 3)时,挂起的副线程将被激活

EventModify(hEvt, 3); // SetEvent(hEvt);

在接收到信号的处理代码里,需要重新将事件设置为未激活状态,否则WaitForSingleObject函数将判定事件为激活状态,不再发生等待

EventModify(hEvt, 2); // ResetEvent(hEvt);

在程序结束处,记得用CloseHandle关闭创建的事件

CloseHandle(hEvt);

使用API函数的事件响应与使用C#的事件类作用相同,因为使用了句柄做事件的标志,就可以与C的代码进行交互,以英创ARM9系列嵌入式主板EM9161的CAN口数据接收线程为例。

设定一个线程用于CAN口的接收,创建一个事件用于通知线程关闭

private Thread revThread;

hCloseEvent = CreateEvent(0, 1, 0, 0); // CreateEvent(NULL,TRUE,FALSE,NULL)

打开CAN口后,通过COM组件接口函数获得CAN的消息事件句柄

hEvent = CAN.CAN_GetRxEvent(hCAN);
hErr = CAN.CAN_GetErrorEvent(hCAN);

设定一个接收线程专门处理CAN口接收。
revThread = new Thread(new ThreadStart(BeginReceive));
threadStop = false;
revThread.Start(); // 启动waitforMessage线程

在接收函数中,执行等待,直到有CAN口接收消息到来,或是接收到线程关闭的事件。

private void BeginReceive() // 客户机状态下接收数据线程
{
int[] handles = new int[2];
handles[0] = hCloseEvent;
handles[1] = hEvent;
int i;
bool bResult;
string revstr;
while (!threadStop)
{
// WaitForSingleObject(hEvent, 200);
i = WaitForMultipleObjects(2, handles, 0, -1); // handles里的两个事件hEvent和hCloseEvent
// ….其他的处理代码

}

}

这里使用了WaitForMultipleObjects来同时等待2个事件,第一个参数为等待的事件数。第二个参数为各事件的数组。第三个参数为FALSE即0表示当任何一个事件产生,该函数即返回,第三个参数为TRUE即1表示只有当所有事件都产生,该函数才返回。最后个参数为等待的时间。返回值为0x102表示超时,返回0-X表示接收的事件在数组中的位置,同时接收多个事件,返回的第一个事件在数组中的位置。

更详细的完整代码,请参考英创ARM9系列嵌入式主板EM9161的CAN事件接口例程。

8、等待线程

C#使用Thread类的Join函数来等待一个线程

using System;
using System.Threading;
namespace thread
{
class Program
{
static int i;
static void Main(string[] args)
{
Thread t = new Thread(excute);
t.Start();
for (i = 0; i < 10; i++)
{
Console.Write('2');
}
t.Join();
// t.Join(1000);
for (; ; )
{
Console.Write('2');
}
}
static void excute()
{
for (; ;)
{
Console.Write('1');
}
}
}
}

该函数不带参数表示一直等待到线程结束,带参数表示等待的时间,返回true表示线程已结束,返回false表示线程还在运行,只是超时返回。

在主函数关闭前,应使用Join函数来确保各支线程已完全关闭,否则会导致进程无法完全关闭。

9、其他

在关闭程序进程时,请确保关闭所有创建的线程,否则进程将无法完全关闭,并一直占用系统资源。在英创ARM9系列嵌入式主板程序开发中,可以结合VS自带的远程线程查看工具进行程序调试。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式主板
    +关注

    关注

    7

    文章

    6085

    浏览量

    35295
收藏 人收藏

    评论

    相关推荐

    新手怎么学嵌入式?

    操作系统示例。 学习嵌入式技术是一个充满挑战但又极具乐趣的过程。对于新手来说,只要有耐心和恒心,按照上述步骤逐步学习,从基础知识到编程语言,从硬件知识到实践操作,再到
    发表于 12-12 10:51

    嵌入式系统与物联网的结合

    随着科技的飞速发展,嵌入式系统和物联网(IoT)已经成为现代技术领域的重要组成部分。嵌入式系统是指嵌入
    的头像 发表于 11-06 10:23 280次阅读

    嵌入式系统的原理和应用

    嵌入式系统是一种专用的计算机系统,其设计初衷是执行特定任务,而非作为通用计算机使用。这类系统通常作为更大系统的一部分,起到控制、监控或辅助的
    的头像 发表于 10-05 17:03 817次阅读

    开启全新AI时代 智能嵌入式系统快速发展——“第六届国产嵌入式操作系统技术与产业发展论坛”圆满结束

    解决方案以及面向新一代机器人控制器的嵌入式操作系统场景应用的商业实践成果。 图9 柯善风在做报告 南京翼辉信息技术有限公司副总经理李孝成做了“翼辉任务关键型软件技术体系赋能智能制
    发表于 08-30 17:24

    嵌入式C编程常用的异常错误处理

    嵌入式C编程中,异常错误处理是确保系统稳定性和可靠性的重要部分。以下是一些常见的异常错误处理方法及其详细说明和示例: 1. 断言 (Ass
    发表于 08-06 14:32

    嵌入式热门领域有哪些?

    过物联网连接各种物理设备和传感器,促使它们之间实现信息的交换和数据的共享。从智能家居到智能城市,物联网的应用不断扩展,为嵌入式系统工程师带来了广泛的就业机会和发展前景。 自动驾驶 自动驾驶
    发表于 07-16 09:23

    嵌入式系统怎么学?

    一系列课程和技术,包括但不限于以下内容: 1、基础知识:学习计算机组成原理、数字电路、模拟电路等基础知识,建立对计算机硬件的认知与理解。 2、编程语言:掌握至少一种嵌入式系统常用的
    发表于 07-02 10:10

    如何提升嵌入式编程能力?

    /C++:大多数嵌入式系统使用CC++编程语言,因此深入学习这两种语言是非常重要的。 8. 理
    发表于 06-21 10:01

    BIG WALNUT大核桃全面响应国家“信”号召,倾力“智”造国产芯片+国产加密安全系统通讯产品

    ,BIG WALNUT大核桃品牌积极响应国家“信”号召,倾力“智”造国产芯片+国产加密安全系统通讯产品,为国家的信息化建设和安全发展贡献自己的力量。 BIG WALNUT大核桃品牌,作为安全
    的头像 发表于 06-18 17:42 538次阅读

    如何成为一名嵌入式C语言高手?

    如何成为一名嵌入式C语言高手? 嵌入式系统是当今科技领域的核心,而C语言则是嵌入式
    发表于 04-07 16:03

    嵌入式编程片上系统是什么

    嵌入式编程片上系统(Embedded Programmable System-on-Chip,或简称EPSoC)是一种特殊的嵌入式系统,它
    的头像 发表于 03-28 15:33 559次阅读

    如何成为一名嵌入式C语言高手?

    如何成为一名嵌入式C语言高手? 嵌入式系统是当今科技领域的核心,而C语言则是嵌入式
    发表于 03-25 14:12

    嵌入式工程师需要掌握哪些技术?

    一些必要的技术能力是至关重要的。在本篇中,我们将讨论入行嵌入式所必须的技术能力。 1.C/C++编程
    发表于 03-04 16:38

    嵌入式软件开发应该掌握哪些知识?

    掌握的知识 1.基础知识 1.1 c/c++编程语言和数据结构 C/C++ 是嵌入式
    发表于 02-19 11:23

    嵌入式学习步骤

    开发。 嵌入式学习步骤总结如下: (1).确定目标平台:选择适合您要开发的嵌入式系统的硬件平台。这取决于您要控制的设备以及您需要执行的任务。 (2).选择编程语言:
    发表于 02-02 15:24