PB -5- Arduino DHT11 sensörü

Arduino’dan gelen sıcaklık ve nem bilgisini grafiksel (chart) olarak gösteriyoruz. Merak etmeyin, aşağıdaki 3 tane .pb uzantılı dosyanın toplam satır sayısı, demo PureBasic editörünün limiti olan 800 satırı geçmiyor.

image

Aşağıdaki kodu Arduino’ya yüklüyoruz.. Öncesinde, kütüphane yöneticisi ile DHT-sensor-library ve Adafruit-Unified-Sensor kütüphanelerini yüklüyoruz.

#include <DHT.h>
#define DHTPIN 2 //DHT pin 
#define DHTTYPE DHT11 
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  Serial.begin(19200);
  dht.begin();
}

void loop() {
  float h = dht.readHumidity(); // Nem 
  float t = dht.readTemperature(); // Sıcaklık 
  Serial.print("TP"); // header
  Serial.print(t);
  Serial.print("@"); // separator
  Serial.println(h); // println satır sonuna CRLF basar
  delay(250);
}


Bu kodu Arduino’ya yükledikten sonra, her 250 milisaniyede bir sıcaklık ve nem bilgisi TP24.30@55 biçiminde Purebasic’e iletilecektir. Sensör sıcaklığı küsuratlı veriyor ama nemi tamsayı olarak veriyor. Forumda dolanırken resimde olduğu gibi sinüs, cosinüs veya başka matematiksel grafikleri zamana bağlı olarak çizebilen bir kod parçası buldum. graph100 isimli forum kullanıcısı yazmış, sanırım fransız kendisi.

image

Kodu biraz oynayarak, aşağıdaki hale getirdim. Kodu chart.pb olarak boş bir dizine kaydedin.

; https://www.purebasic.fr/english/viewtopic.php?t=60077&start=18
; chart module from user : graph100 in forum
DeclareModule Graph
  
  Structure Graph_pt_point_d
    x.d
    y.d
  EndStructure
  
  Structure Graph_pt
    *graph.Graph
    color.l
    line.b
    List pt.d()
  EndStructure
  
  Structure Graph
    pos.Graph_pt_point_d
    dimension.POINT
    nb_point.l
    List list_des_pt.Graph_pt()
    maxi.d
    mini.d
    ratio_w.d
    ratio_h.d
    Name.s
  EndStructure
  
  Declare.i Graph_Init(x, y, width, height, nb_point, Name.s = "")
  Declare.i Graph_AddSerie(*graph.Graph, color.l, Line.b = #False)
  Declare.i Graph_FreeSerie(*graph.Graph, *adresse_serie)
  Declare Graph_AddPoint(*adresse_serie.Graph_pt, value.d)
  Declare Graph_Draw(*graph.Graph)
EndDeclareModule

Module Graph
  
  Procedure.d Maxi_delta(value.d, delta.d)
    If delta = 0
      ProcedureReturn Round(value, #PB_Round_Up) + 0.5
    Else
      p10.d = Pow(10, Round(Log10(Abs(delta)), #PB_Round_Down))
      ProcedureReturn Round(value / p10, #PB_Round_Up) * p10
    EndIf
  EndProcedure
  
  Procedure.d Mini_delta(value.d, delta.d)
    If delta = 0
      ProcedureReturn Round(value, #PB_Round_Down) - 0.5
    Else
      p10.d = Pow(10, Round(Log10(Abs(delta)), #PB_Round_Down))
      ProcedureReturn Round(value / p10, #PB_Round_Down) * p10
    EndIf
  EndProcedure
  
  Procedure.i Graph_Init(x, y, width, height, nb_point, Name.s = "")
    *graph.Graph = AllocateMemory(SizeOf(Graph))
    InitializeStructure(*graph, Graph)
    *graph\pos\x = x
    *graph\pos\y = y
    *graph\dimension\x = width
    *graph\dimension\y = height
    *graph\nb_point = nb_point
    *graph\ratio_w = *graph\dimension\x / *graph\nb_point
    *graph\Name = Name
    ProcedureReturn *graph
  EndProcedure
  
  Procedure.i Graph_AddSerie(*graph.Graph, color.l, Line.b = #False) ; serinin adresini döndürür
    *new_serie = AddElement(*graph\list_des_pt())
    InitializeStructure(*new_serie, Graph_pt)
    *graph\list_des_pt()\color = color
    *graph\list_des_pt()\graph = *graph
    *graph\list_des_pt()\line = Line
    ProcedureReturn *new_serie
  EndProcedure
  
  Procedure.i Graph_FreeSerie(*graph.Graph, *adresse_serie) 
    ChangeCurrentElement(*graph\list_des_pt(), *adresse_serie)
    FreeList(*graph\list_des_pt()\pt())
    DeleteElement(*graph\list_des_pt())
  EndProcedure
  
  Procedure Graph_AddPoint(*adresse_serie.Graph_pt, value.d)
    LastElement(*adresse_serie\pt())
    AddElement(*adresse_serie\pt())
    *adresse_serie\pt() = value
    
    If ListSize(*adresse_serie\pt()) > *adresse_serie\graph\nb_point
      FirstElement(*adresse_serie\pt())
      DeleteElement(*adresse_serie\pt())
    EndIf
  EndProcedure
  
  Procedure Graph_Draw(*graph.Graph)
    Line(*graph\pos\x - 1, *graph\pos\y - 1, *graph\dimension\x + 2, 1, #Green)
    Line(*graph\pos\x - 1, *graph\pos\y - 1, 1, *graph\dimension\y + 2, #Green)
    Line(*graph\pos\x - 1, *graph\pos\y - 1 + *graph\dimension\y + 2, *graph\dimension\x + 2, 1, #Green)
    Line(*graph\pos\x - 1 + *graph\dimension\x + 2, *graph\pos\y - 1, 1, *graph\dimension\y + 2, #Green)
    
    j = 4
    For i = 69 To 621 Step 69
      Line(*graph\pos\x + i, *graph\pos\y  + *graph\dimension\y - 3, 1, 10, #Green)
      DrawText(*graph\pos\x + i - 6, *graph\pos\y  + *graph\dimension\y + 8, Str(j), #Green, 0)
      j + 4 
    Next
    DrawText(*graph\pos\x + 640, *graph\pos\y  + *graph\dimension\y + 8, "secs", #Green, 0)
    
    If *graph\Name
      Protected titre$ = " " + *graph\Name + " "
      Protected w = TextWidth(titre$), h = TextHeight(titre$)
      Protected x.d = *graph\pos\x + *graph\dimension\x / 2 - w / 2, y.d = *graph\pos\y - h / 2
      DrawText(x ,y , titre$, #Green)
      Line(x - 1, y - 1, w + 1, 1, #Green)
      Line(x - 1, y - 1, 1, h + 1, #Green)
      Line(x - 1, y - 1 + h + 1, w + 2, 1, #Green)
      Line(x - 1 + w + 1, y - 1, 1, h + 2, #Green)
    EndIf
    
    DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y - 1, StrD(*graph\maxi), #Green)
    DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y - 1 + *graph\dimension\y +
                                                    2 - TextHeight(" "), StrD(*graph\mini), #Green)
    *graph\ratio_h = *graph\dimension\y / (*graph\maxi - *graph\mini)
    mini.d = *graph\mini
    
    If Sign(*graph\maxi) <> Sign(*graph\mini)
      Line(*graph\pos\x, *graph\pos\y + *graph\dimension\y + mini * *graph\ratio_h, *graph\dimension\x, 1, #Green)
      DrawText(*graph\pos\x + *graph\dimension\x + 2, *graph\pos\y + *graph\dimension\y +
                                                      mini * *graph\ratio_h - TextHeight(" ") / 2, "0", #Green)
    EndIf
    
    *graph\maxi = -Infinity()
    *graph\mini = Infinity()
    
    ForEach *graph\list_des_pt()
      index = 0
      If *graph\list_des_pt()\line = #False
        ForEach *graph\list_des_pt()\pt()
          If *graph\list_des_pt()\pt() > *graph\maxi : *graph\maxi = *graph\list_des_pt()\pt() : EndIf
          If *graph\list_des_pt()\pt() < *graph\mini : *graph\mini = *graph\list_des_pt()\pt() : EndIf
          Line(*graph\pos\x + index * *graph\ratio_w, *graph\pos\y + 
                                                      *graph\dimension\y - (*graph\list_des_pt()\pt() - mini)**graph\ratio_h,
               1, 1, *graph\list_des_pt()\color)
          index + 1
        Next
        
      Else
        *adr.Double = FirstElement(*graph\list_des_pt()\pt())
        old_x.d = *graph\pos\x + index * *graph\ratio_w
        old_y.d = *graph\pos\y + *graph\dimension\y - (*graph\list_des_pt()\pt() - mini) * *graph\ratio_h
        ForEach *graph\list_des_pt()\pt()
          If *graph\list_des_pt()\pt() > *graph\maxi : *graph\maxi = *graph\list_des_pt()\pt() : EndIf
          If *graph\list_des_pt()\pt() < *graph\mini : *graph\mini = *graph\list_des_pt()\pt() : EndIf
          x.d = *graph\pos\x + index * *graph\ratio_w
          y.d = *graph\pos\y + *graph\dimension\y - (*graph\list_des_pt()\pt() - mini) * *graph\ratio_h
          LineXY(x, y, old_x, old_y, *graph\list_des_pt()\color)
          old_x = x
          old_y = y
          index + 1
        Next
      EndIf
      
      If LastElement(*graph\list_des_pt()\pt())
        DrawText(*graph\pos\x + *graph\dimension\x + 2,
                 *graph\pos\y + *graph\dimension\y-(*graph\list_des_pt()\pt() - mini) * *graph\ratio_h - TextHeight(" ") / 2, 
                 StrD(*graph\list_des_pt()\pt()), *graph\list_des_pt()\color)
      EndIf
    Next
    
    delta.d = *graph\maxi - *graph\mini
    *graph\maxi = Maxi_delta(*graph\maxi, delta)
    *graph\mini = Mini_delta(*graph\mini, delta)
    If delta = 0 : delta = 1 : EndIf
    
  EndProcedure
EndModule


Geçen yazıda olduğu gibi burada da Mk-soft’un seri port’u thread ile okuyan kodunu kullandım. Aşağıdaki kodu da comPort.pb olarak aynı dizine kaydedin.

;-TOP
; https://www.purebasic.fr/english/viewtopic.php?t=79759
; Comment : Comport Manager Over Thread and Callback
; Author  : mk-soft
; Version : v0.07.1
; Created : 26.01.2018
; Updated : 03.09.2022

; *****************************************************************************

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Option Threadsafe!"
CompilerEndIf

Prototype ProtoReceiveCB(Text.s)
Prototype ProtoStatusCB(Status, *ComData)

Enumeration
  #ComThread_Stopped
  #ComThread_Startup
  #ComThread_Running
EndEnumeration

Enumeration
  #ComStatus_Nothing
  #ComStatus_OpenPort
  #ComStatus_ClosePort
  #ComStatus_ErrorOpenPort
  #ComStatus_ErrorSend
  #ComStatus_ErrorReceive
  #ComStatus_ErrorDataSize
EndEnumeration

Structure udtComData
  ; Header
  ThreadID.i
  Exit.i
  Status.i
  ; Port Data
  ComID.i
  Port.s
  Baud.i
  Parity.i
  DataBit.i
  StopBit.i
  Handshake.i
  BufferSize.i
  ; End Of Text
  EndOfText.s
  ; Send Data
  SendSignal.i
  SendCount.i
  SendText.s
  SendError.i
  ; Receive data
  ReceiveCount.i
  ReceiveText.s
  ReceiveError.i
  ; Callback
  *StatusCB.ProtoStatusCB
  *ReceiveCB.ProtoReceiveCB
EndStructure

Procedure thComport(*ComData.udtComData)
  
  Protected *Send, *Receive, SendText.s, SendLen, ReceiveText.s, ReceiveLen, Pos
  
  With *ComData
    ; Startup
    \Status = #ComThread_Startup
    \SendCount = 0
    \ReceiveCount = 0
    \ComID = OpenSerialPort(#PB_Any, \Port, \Baud, \Parity, \DataBit, \StopBit, \Handshake, \BufferSize, \BufferSize)
    If \ComID
      \Status = #ComThread_Running
    Else
      If \StatusCB
        \StatusCB(#ComStatus_ErrorOpenPort, *ComData)
      EndIf
      \Status = #ComThread_Stopped
      ProcedureReturn 0
    EndIf
    If \StatusCB
      \StatusCB(#ComStatus_OpenPort, *ComData)
    EndIf
    *Send = AllocateMemory(\BufferSize)
    *Receive = AllocateMemory(\BufferSize)
    ; Loop
    Repeat
      If \SendSignal
        SendText = \SendText + \EndOfText
        SendLen = StringByteLength(SendText, #PB_Ascii)
        If SendLen <= \BufferSize
          PokeS(*Send, SendText, SendLen, #PB_Ascii)
          If WriteSerialPortData(\ComID, *Send, SendLen) = 0
            \SendError = SerialPortError(\ComID)
            If \StatusCB
              \StatusCB(#ComStatus_ErrorSend, *ComData)
            EndIf
          Else
            \SendError = 0
            \SendCount + 1
          EndIf
        Else
          If \StatusCB
            \StatusCB(#ComStatus_ErrorDataSize, *ComData)
          EndIf
        EndIf
        \SendSignal = #False
      EndIf
      ReceiveLen = AvailableSerialPortInput(\ComID)
      If ReceiveLen
        ReceiveLen = ReadSerialPortData(\ComID, *Receive, ReceiveLen)
        If ReceiveLen = 0
          \ReceiveError = SerialPortError(\ComID)
          If \StatusCB
            \StatusCB(#ComStatus_ErrorReceive, *ComData)
          EndIf
        Else
          \ReceiveError = 0
        EndIf
        ReceiveText + PeekS(*Receive, ReceiveLen, #PB_Ascii)
        Repeat
          pos = FindString(ReceiveText, \EndOfText, 1, #PB_String_NoCase)
          If pos
            \ReceiveText = Left(ReceiveText, pos - 1)
            ReceiveText = Mid(ReceiveText, pos + Len(\EndOfText))
            \ReceiveCount + 1
            If \ReceiveCB
              \ReceiveCB(\ReceiveText)
            EndIf
          EndIf
        Until pos = 0
      EndIf
      Delay(10)
    Until \Exit
    ; Shutdown
    If IsSerialPort(\ComID) : CloseSerialPort(\ComID) : EndIf 
    If \StatusCB
      \StatusCB(#ComStatus_ClosePort, *ComData)
    EndIf
    FreeMemory(*Send)
    FreeMemory(*Receive)
    \Status = #ComThread_Stopped
    \ComID = 0
    \Exit = 0
    ProcedureReturn 1
  EndWith
  
EndProcedure

; ----

Procedure.s SerialPortErrorText(ErrorCode)
  Protected r1.s
  
  Select ErrorCode
    Case #PB_SerialPort_RxOver      : r1 = "An input buffer overflow has occurred."
    Case #PB_SerialPort_OverRun     : r1 = "A character-buffer overrun has occurred."
    Case #PB_SerialPort_RxParity    : r1 = "The hardware detected a parity error."
    Case #PB_SerialPort_Frame       : r1 = "The hardware detected a framing error."
    Case #PB_SerialPort_Break       : r1 = "The hardware detected a break condition."
    Case #PB_SerialPort_TxFull      : r1 = "The application tried to transmit a character but the output buffer was full."
    Case #PB_SerialPort_IOE         : r1 = "An I/O error occurred during communications with the device."
    Case #PB_SerialPort_WaitingCTS  : r1 = "Specifies whether transmission is waiting for the CTS (clear-To-send) signal to be sent."
    Case #PB_SerialPort_WaitingDSR  : r1 = "Specifies whether transmission is waiting for the DSR (Data-set-ready) signal to be sent."
    Case #PB_SerialPort_WaitingRLSD : r1 = "Specifies whether transmission is waiting for the RLSD (receive-line-signal-detect) signal to be sent."
    Case #PB_SerialPort_XoffReceived: r1 = "Specifies whether transmission is waiting because the XOFF character was received."
    Case #PB_SerialPort_XoffSent    : r1 = "Specifies whether transmission is waiting because the XOFF character was transmitted."
    Case #PB_SerialPort_EOFSent     : r1 = "Specifies whether the end-of-file (EOF) character has been received."
    Default                         : r1 = "ErrorCode " + Hex(ErrorCode)
  EndSelect
  ProcedureReturn r1
EndProcedure

; ----

; Threaded String Helper

Procedure AllocateString(String.s)
  Protected *mem
  *mem = AllocateMemory(StringByteLength(String) + SizeOf(Character))
  If *mem
    PokeS(*mem, String)
  EndIf
  ProcedureReturn *mem
EndProcedure

Procedure.s FreeString(*Mem)
  Protected result.s
  If *Mem
    result = PeekS(*Mem)
    FreeMemory(*Mem)
  EndIf
  ProcedureReturn result
EndProcedure


XIncludeFile komutu ile üstteki dosyaları kullanan aşağıdaki kodu da dht11.pb olarak aynı dizine kaydedin.

XIncludeFile "chart.pb"
XIncludeFile "comPort.pb"

  
Global ComData.udtComData, temperature$, humidity$
Define.f maxTemperature, maxHumidity
  
  Enumeration EventCustomValue #PB_Event_FirstCustomValue
    #My_Event_NewData
    #My_Event_NewState
  EndEnumeration
  
  ; ---------------------------------------------------------------------------  

    Procedure ReceiveCB(Text.s)
    PostEvent(#My_Event_NewData, 0, 0, 0, AllocateString(Text))
  EndProcedure
  
  Procedure MyEventNewDataCB()
    Protected Text.s
    Text = FreeString(EventData())
    If Left(Text, 2) = "TP"
      temperature$ = StringField(Text, 1, "@")
      temperature$ = Right(temperature$, (Len(temperature$) - 2))
      humidity$ = Right(Text, Len(text) - (Len(temperature$) + 3))
    EndIf 
  EndProcedure

  BindEvent(#My_Event_NewData, @MyEventNewDataCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure StatusCB(Status, *ComData.udtComData)
    PostEvent(#My_Event_NewState, 0, 0, Status, *ComData)
  EndProcedure
  
  Procedure MyEventNewStateCB()
    Protected Text.s, Status, *ComData.udtComData
    Status = EventType()
    *ComData = EventData()
    Select Status
      Case #ComStatus_OpenPort
        Text = "ComStatus: Open Port " + *ComData\Port
      Case #ComStatus_ClosePort
        Text = "ComStatus: Close Port " + *ComData\Port
      Case #ComStatus_ErrorOpenPort
        Text = "ComError: Open Port " + *ComData\Port
      Case #ComStatus_ErrorSend
        Text = "ComError Send: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\SendError)
      Case #ComStatus_ErrorReceive
        Text = "ComError Receive: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\ReceiveError)
      Case #ComStatus_ErrorDataSize
        Text = "ComError Send: Port " + *ComData\Port + " - Send data size to big."
    EndSelect
    If Bool(Text)
      StatusBarText(0, 0, Text)
    EndIf
  EndProcedure
  
  BindEvent(#My_Event_NewState, @MyEventNewStateCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure InitComport()
    With ComData
      If \Status
        ProcedureReturn 2 ; Always running
      EndIf
      \Port = "COM3"
      \Baud = 19200
      \Parity = #PB_SerialPort_NoParity
      \DataBit = 8
      \StopBit = 1
      \Handshake = #PB_SerialPort_NoHandshake
      \BufferSize = 2048
      \EndOfText = #CRLF$
      \StatusCB = @StatusCB()
      \ReceiveCB = @ReceiveCB()
      \ThreadID = CreateThread(@thComport(), ComData)
      If Not \ThreadID
        ProcedureReturn 0 ; Error create thread
      Else
        ProcedureReturn 1 ; ok
      EndIf
    EndWith
  EndProcedure
  
  ; ---------------------------------------------------------------------------

OpenWindow(0, 0, 0, 770, 620, "Arduino Hava istasyonu", #PB_Window_ScreenCentered | #PB_Window_SystemMenu)
CanvasGadget(0, 10, 10, 750, 580)
CreateStatusBar(0, WindowID(0))
AddStatusBarField(#PB_Ignore)

AddWindowTimer(0, 1, 2000)

InitComport()

UseModule Graph

;{ init graphics
; 1000 olan nokta sayısı değişirse x ekseni daralır ya da genişler
*graph.Graph = Graph_Init(10, 20, 700, 200, 1000, "Temperature")
*ad_temperature = Graph_AddSerie(*graph, #Red, #True)
;*ad_cosinus = Graph_AddSerie(*graph, RGB(200, 200, 255), #True) ; 2saniye testi

*graph_humidity.Graph = Graph_Init(10, 290, 700, 200, 1000, "Humidity")
*ad_humidity = Graph_AddSerie(*graph_humidity, #Blue, #True)
;}

_dt_lim = 40 ; bunu değiştirince x ekseni daralır ya da genişler
time = ElapsedMilliseconds()

a=-3

  ; ---------------------------------------------------------------------------
Repeat
  
  ;{ event
  Repeat
    event = WindowEvent()
    Select event
        
      Case #PB_Event_Timer		
        If EventTimer() = 1
          If a=-3 : a=3 : ElseIf a=3 : a=-3 : EndIf
        EndIf
    EndSelect
    
    If event = #PB_Event_CloseWindow
      If ComData\Status
        ComData\Exit = 1
      EndIf 
      End
    EndIf
    
  Until event = 0
  ;}
  
  ;{ fps ayarı
  dt = ElapsedMilliseconds() - time
  If dt < _dt_lim
    Delay(_dt_lim - dt) 
  EndIf
  dt = ElapsedMilliseconds() - time
  time = ElapsedMilliseconds() 
  ;}
  Graph_AddPoint(*ad_temperature, ValF(temperature$) ) ;Cos(2* 3.14 * time)) 
  ;Graph_AddPoint(*ad_cosinus, a) ; 2saniye testi 
  Graph_AddPoint(*ad_humidity, ValF(humidity$))
  ;{ chart çizimi
  StartDrawing(CanvasOutput(0))
  Box(0, 0, OutputWidth(), OutputHeight(), 0)
  Graph_Draw(*graph)
  Graph_Draw(*graph_humidity)
  If maxTemperature < ValF(temperature$) : maxTemperature = ValF(temperature$) : EndIf
  If maxHumidity < ValF(humidity$) : maxHumidity = ValF(humidity$) : EndIf
  DrawText(150, 540, "Max. Temperature : " + StrF(maxTemperature,2) , #Green, 0)
  DrawText(450, 540, "Max. Humidity : " + StrF(maxHumidity,2) , #Green, 0)
  StopDrawing()
  ;}
  
ForEver



image

Bu grafiği görebilmek kolay olmadı. Çocukların cama resim yapmak için yaptıkları gibi sensöre biraz hohlamam gerekti. 😄

Siteyi, About sekmesinden başlayarak okuyun. Yazıları da sırayla okuyun. Aksi halde önceki yazılarda belirttiğim püf noktaları kaçırmış olursun.

VB veya C# .Net ile karşılaştırmak gerekirse PB’de kodlar biraz uzuyor. Ama daha anlaşılır olarak görüyorum ben. .Net’de herşeyin kütüphanesini bulmak mümkün, bazı basit işlemler bile PB’de kodlama yapılarak çözülüyor. Böylece konuyu daha temelden anlamış oluyorsun. Forumda hertürlü kodu bulabiliyorsun. Bulamasanız bile foruma sorabiliyorsun.

PB’in güzelliği de şu ki, programın, .Net veya Java’da olduğu gibi başka bir kütüphaneye bağımlı olarak değil tamamen kendi kendine çalışıyor olmasıdır. Yukarıdaki kodları alın, PB editörüne yapıştırın. Program çalışacaktır. İşletim sistemi Windows 7, 8, 9, 10, 11 olsa da bu kod hepsinde çalışır. Windows 9 diye birşey olmadığını biliyorum 😄 Neden 8’den 10’a atladılar ? Bu soru hep beynimi tırmalamıştır 🧗

PB’in bir başka güzelliği de thread konusunun sade ve basit olmasıdır. Örneğin bu işi .Net ile yapsaydık, seri porttan gelen bilginin grafiğe veya forma aktarılması için Delegate, Invoke gibi komutlar kullanmak gerekecekti. .Net için thread konusunu araştırırsan, çok karmaşık ve zor olduğunu görürsün. Sadece thread konusunu anlatan .Net kitaplarının yazıldığına şahit olabilirsin. PB’de ise thread, çok çok daha basit ve anlaşılır..

Written on September 27, 2024