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

    文章

    6064

    浏览量

    34605
收藏 人收藏

    评论

    相关推荐

    嵌入式热门发展方向有哪些?

    已经体现了嵌入式系统的优势;在信息家电领域,冰箱、空调等的网络化、智能化将引领人们的生活步入一个崭新的空间,即使你不在家里,也可以通过电话线、网络进行远程控制,在这些设备中,嵌入式
    发表于 04-11 14:17

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

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

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

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

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

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

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

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

    嵌入式人工智能的就业方向有哪些?

    操作系统使用及C高级编程 数据结构与算法 二:嵌入式Linux应用工程师,Linux网络开发工程师 文件IO 并发程序设计 项目实战:基于Linux的Flappybird游戏开发 网络
    发表于 02-26 10:17

    嵌入式系统发展前景?

    应用领域。随着汽车电子化和智能化程度的不断提高,嵌入式系统将在汽车控制、安全系统、自动驾驶等方面发挥更为重要的作用。 工智能和机器学习技术的发展为
    发表于 02-22 14:09

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

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

    嵌入式学习步骤

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

    如何成为一名优秀的嵌入式工程师?

    开发的嵌入式系统的架构、特点和性能。这有助于你更好地理解系统的需求,选择合适的技术方法来满足系统
    发表于 11-07 15:36

    基于嵌入式技术的网络开票系统的设计方法

    电子发烧友网站提供《基于嵌入式技术的网络开票系统的设计方法.pdf》资料免费下载
    发表于 11-06 10:18 0次下载
    基于<b class='flag-5'>嵌入式</b><b class='flag-5'>技术</b>的网络开票<b class='flag-5'>系统</b>的设计<b class='flag-5'>方法</b>

    嵌入式C/C++语言精华文章集锦

    体可以将原本意义属于一个整体的数据组合在一起。从某种程度上来说,会不会用 struct,怎样用struct 是区别一个开发人员是否具备丰富开发经历的标志。 在网络协议、通信控制、嵌入式系统C
    发表于 09-25 08:00

    嵌入式编程和微控制器应用

    对AtmelAVRRISC处理器的介绍,读者会立即进入嵌入式C语言教程之中。在本书中,读者将学习C语言的变量和常量、运算符和表达式、控制语句、指针和数组、存储器类型、预处理指令、实时方法
    发表于 09-20 07:52

    嵌入式学习路线你知道吗?

    嵌入式学习路线你知道吗? 1打好嵌入式软件编程的基础 这一阶段重点打好嵌入式软件编程的基础,包括学习Linux
    发表于 06-14 16:00

    嵌入式用什么语言编程?

    嵌入式用什么语言编程? 嵌入式系统应用越来越广泛,专业的嵌入式工程师对这样的问题肯定不会太关注,只是针对一些初级入门者,做个简单的了解,认识
    发表于 05-18 10:08