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

    文章

    6107

    浏览量

    36935
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C语言在嵌入式开发中的应用

    语言可以确保实时系统中的任务能够在规定的时间内完成,保证系统的稳定性和可靠性。 4、嵌入式网络编程 随着物联网技术的飞速发展,
    发表于 11-21 08:09

    嵌入式和FPGA的区别

    。 在当今智能化时代,嵌入式系统和FPGA技术都是电子系统设计中的重要组成部分,但许多工程师和技术爱好者常常对两者的区别和应用场景感到困惑
    发表于 11-19 06:55

    2025年最佳的嵌入式编程语言有哪些呢?

    嵌入式系统是现代科技不可或缺的一部分。它们存在于家用电器、汽车、住宅、医院、商店等各个领域。它们与我们的社会紧密相连。选择合适的语言来构建嵌入式系统对于成功至关重要。那么,2025年最
    的头像 发表于 11-14 10:27 885次阅读
    2025年最佳的<b class='flag-5'>嵌入式</b><b class='flag-5'>编程</b>语言有哪些呢?

    嵌入式开发的关键点介绍

    嵌入式开发在现代科技中扮演着非常重要的角色。随着物联网的发展,嵌入式系统的需求也越来越大。嵌入式开发不仅需要开发人员具备深入的硬件知识和编程
    发表于 11-13 08:12

    嵌入式实时操作系统的特点

    。 低功耗和小尺寸:实时嵌入式操作系统通常要求运行时的资源占用较少,以适应嵌入式系统对功耗和尺寸的限制。 常见的实时嵌入式操作
    发表于 11-13 06:30

    盘点嵌入式就业所需要的技能有哪些?

    ,把握未来的职业机遇。 1.智能汽车行业: - 熟悉嵌入式编程语言,如C/C++、Python等。 - 掌握嵌入式
    发表于 08-11 15:43

    入行嵌入式应该怎么准备?

    知识: 一、C/C++编程C/C++是嵌入式系统
    发表于 08-06 10:34

    嵌入式和单片机,是同一个东西吗?

    (RTOS)或没有任何操作系统的裸机编程。软件方面,嵌入式系统可能使用专门的编程语言,如C
    发表于 07-09 10:20

    嵌入式编程设计模式

    嵌入式编程设计模式,介绍如何使用设计模式为嵌入式系统创建高效且优化的C语言设计。 纯分享贴,有需要可以直接下载附件获取完整资料! (如
    发表于 04-15 14:47

    嵌入式系统中的代码优化与压缩技术

    字符串,从而减少存储空间。在解压时,系统通过相应的解码算法还原数据。 代码去重:在大型嵌入式项目中,可能存在重复的代码片段。通过代码去重技术,识别并合并这些重复代码,不仅减少了代码体积,也方便后期维护
    发表于 02-26 15:00

    如何提高嵌入式代码质量?

    嵌入式代码的质量是至关重要的。本文将探讨如何通过有效的开发方法和工具来提高嵌入式代码的质量,以确保系统的可靠性和可维护性。 理解嵌入式
    发表于 01-15 10:48

    哪些专业适合学习嵌入式开发?

    市场的80%以上,那什么专业能够轻松转行呢? 1)计算机科学与技术:作为基础学科,计算机科学与技术专业提供了编程、算法、数据结构等基础知识,适合转行到嵌入式、人工智能和物联网领域。 2
    发表于 01-03 13:44

    运动控制卡网络通讯的心跳检测之C#上位机编程

    使用C#上位机编程实现运动控制卡网络通讯的心跳检测功能
    的头像 发表于 12-24 10:54 1958次阅读
    运动控制卡网络<b class='flag-5'>通讯</b>的心跳检测之<b class='flag-5'>C#</b>上位机<b class='flag-5'>编程</b>

    新手怎么学嵌入式?

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

    嵌入式系统开发与硬件的关系 嵌入式系统开发常见问题解决

    系统开发与硬件关系的几个关键点: 硬件依赖性 :嵌入式系统的软件必须能够在特定的硬件上运行,这包括处理器、内存、输入/输出接口等。软件必须能够充分利用硬件的特性,同时绕过其限制。 资源限制 :
    的头像 发表于 12-09 09:38 1464次阅读