I am trying to call Advapi32 GetThreadWaitChain (WCT) function from my c# code, I've struggled previously on similar issue:
WCT GetThreadWaitChain call allways return false
However now I am trying to make this call as async call with callback, as documented here:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681421(v=vs.85).aspx
That's my finction:
internal void CollectWaitAsyncInformation(ClrThread thread)
{
//Currenlty not working
var handle = Advapi32.OpenThreadWaitChainSession(WCT_SESSION_OPEN_FLAGS.WCT_ASYNC_OPEN_FLAG, AppCallback);
uint threadID = thread.OSThreadId;
WAITCHAIN_NODE_INFO[] NodeInfoArray = new WAITCHAIN_NODE_INFO[WctApiConst.WCT_MAX_NODE_COUNT];
int isCycle = 0;
int Count = WctApiConst.WCT_MAX_NODE_COUNT;
_eventHandler = Kernel32.CreateEvent(IntPtr.Zero, true, true, "MyEvent");
//This is where the applciation hangs
bool waitChainResult = Advapi32.GetThreadWaitChain(handle,
_eventHandler, 0 ,
threadID, ref Count, NodeInfoArray, out isCycle);
CheckCount(ref Count);
if (!waitChainResult)
{
var lastErrorCode = WinApi.Advapi32.GetLastError();
if (lastErrorCode == (uint)SYSTEM_ERROR_CODES.ERROR_IO_PENDING)
{
// Wait for callback to run...
WinApi.Kernel32.WaitForSingleObject(_eventHandler, int.MaxValue);
}
Kernel32.WaitForSingleObject(_eventHandler, uint.MaxValue);
}
}
link:
https://github.com/Pavel-Durov/Multithreading-Debugging-Asignments/blob/master/Assignments/Assignments.Core/Handlers/WCT/WctApiHandler.cs
My process hangs as I am calling Advapi32.GetThreadWaitChain function without any error or such.
Last time the problem was with the WAITCHAIN_NODE_INFO struct layout, however here I am using the same struct so I don't know if it is an issue, but if I pass IntPtr.Zero instead on NodeInfo array I get false as a result but GetLastError returns me 0.
Any help will be appreciated :).
Here is the link for my whole GitHub project:
https://github.com/Pavel-Durov/Multithreading-Debugging-Asignments
Related
I had a hard time finding how to use HOperatorSet.SetDrawingObjectCallback(HTuple drawID, HTuple drawObjectEvent, HTuple callbackFunction) (Docu) in C#, specifically the part of the callback HTuple callbackFunction. Apart from a Chinese website (Link), I could not locate any examples on how to properly do this. The website itself was also not straight forward to find and the code used there throws a fatal exception. In order for other people to have a better resource on how to use the HOperatorSet.SetDrawingObjectCallback method, I decided to create this question and answer it myself.
The key here is that the HTuple callbackFunction is a function pointer that points to a delegate of the type HDrawingObject.HDrawingObjectCallback. This delegate then links to you proper callback method. The callback method includes an intptr to the drawingId and the windowId as well as a string type.
Here is the code snippet, that hopefully will make your day better:
public class SomeClass
{
public SomeClass()
{
DrawingObjectCallback = DrawingObjectCallbackHandler;
}
public void AddDrawingObject()
{
HOperatorSet.CreateDrawingObjectCircle(50, 50, 100, out var drawId);
//Get the pointer to HDrawingObjectCallback which links to our handler
var ptr = Marshal.GetFunctionPointerForDelegate(DrawingObjectCallback);
//Select the events you want to listen to
var listenTo = new HTuple("on_resize", "on_drag");
//Finally call the method
HOperatorSet.SetDrawingObjectCallback(drawId, listenTo, ptr);
HOperatorSet.AttachDrawingObjectToWindow(HalconWindow.HalconID, drawId);
}
public HDrawingObject.HDrawingObjectCallback DrawingObjectCallback { get; }
public void DrawingObjectCallbackHandler(IntPtr drawId, IntPtr windowHandle, string type)
{
int id = new HTuple(drawId);
int windowId = new HTuple(windowHandle);
//Now you have the two Ids as integers and can work from here!
}
}
I have a native dll written in C. One function is to search for Gateway and return after timeout.
In C codes,it's a synchronous process
search -> callback -> return bool.
So by the time find_gw finished, the gw_found has already been called thus we already have the results. Is there a proper way to sync this so that I can get gwaddr gwname right after find_gw? And I numberred the order it run. gw_found is called inside find_gw synchronously
var gw_found = new FOUND_GW_CALLBACK(callback) //1.new delegate
bool pinvokereturn=GwPInvokes.find_gw(broadcastaddr, timeout, gw_found);//2.pinvoke
//4.How to get gwaddr/gwname here
where
public static bool callback(IntPtr ptr,string gwddr,string gwname)
{
return true; //3.callback
}
I've created a simple c# COM component that is meant to be called by a C++ MFC application. It works but has an undesired side-effect.
The C++ client calls the c# component using it's main GUI thread, so I can't block it because the C# code is making Database operations that may take longer. That's why i need async or a thread for this...
This is the C# code, simplified:
public async void ShowOverviewDialogAsync()
{
var w = new Window();
var dbOperationOk = await LongDbOperaionAsync();
w.ShowDialog();
}
Well, the C++ code goes in, and returns after the call to async which is expected. Here is the C++ calling code:
HRESULT hr = cSharpCom.CoCreateInstance(__uuidof(CSharpCom));
if (SUCCEEDED(hr))
{
cSharpCom->ShowOverviewDialogAsync();
}
// continues without waiting for the dialog close...
The thing is, after this call, the C++ code continues to do its thing and the cSharpCom object goes out of scope. So the only way to call a Release is to make the c# object a member and on destructing or creating a new one, do a release call:
if (_cSharpCom != NULL) _cSharpCom->Release();
HRESULT hr = _cSharpCom.CoCreateInstance(__uuidof(CSharpCom));
if (SUCCEEDED(hr))
{
_cSharpCom->ShowOverviewDialogAsync();
}
This is the first Drawback. The second drawback is that the modal C# window is not a real Modal window as the message pooling of the MFC(C++) continues as normal, which is actually undesired (Right?)
These are the current ideas:
return a HWND to the calling code and somehow make the modal call using the C++ Cwnd:RunModalLoop.
return some kind of event to the calling code, that can be waited upon without blocking the GUI thread.
set a callback that will make sure the COM object will be released.
override the Task class so that it returns a COM aware Interface that allows the C++ code to wait on something (see 2. and 3.)
Number 4 is what I will probably try out. The COM signature would look like:
HRESULT _stdcall ShowOverviewDialogAsync([out, retval] IUnknown** pRetVal);
Just for the lack of completeness, if the method has no async, the MFC C++ GUI hread works as expected and shows the Window in a modal way:
public void ShowOverviewDialogAsync()
{
var w = new Window();
var dbOperationOk = LongDbOperaion();
w.ShowDialog(); // C++ will wait here - non-blocking
}
If you are asking why my async method is returning void. Well, it's defined in an interface and I can't use Task as a return value, unless I go for the solution 4 above.
[ComVisible(true)]
[Guid("XXXXXX-xxxx-xxx-xxx-XXXXX")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICSharpCom
{
/// <summary>
/// Displays the dialog in async mode
/// </summary>
[DispId(0)]
void ShowOverviewDialogAsync();
}
Besides, COM interop does not accept generics like Task.
How to test it. Create a MFC application with one button that calls the c# com as defined.
Any ideas appreciated.
cheers,
Marco
I've managed to get it working, although I'm not very happy with the spaghetti code needed in c++.
This is how I've managed until now:
[ComVisible(true)]
[Guid("XXXXXX-xxxx-xxx-xxx-XXXXX")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICSharpCom
{
/// <summary>
/// Displays the dialog in async mode
/// </summary>
[DispId(0)]
void ShowOverviewDialogAsync(IntPtr eventHandle);
}
//Implementation:
...
_resetEvent = new ManualResetEvent(false);
m.SafeWaitHandle = new Microsoft.Win32.SafeHandles.SafeWaitHandle(eventHandle, ownsHandle);
// ... at some later point in the method, when the window closes
_resetEvent .Set()
Until now, looks ok.
On the C++ side it gets complicated:
auto handle = CreateEvent(NULL, TRUE, FALSE, CString("MyEvent"));
if (handle != NULL)
{
HANDLE handles[] = { handle };
hr = prescOverview->ShowOverviewDialog((long)handle);
auto handleCount = _countof(handles);
if (SUCCEEDED(hr))
{
BOOL running = TRUE;
do {
DWORD const res = ::MsgWaitForMultipleObjectsEx(
handleCount,
handles,
INFINITE,
QS_ALLINPUT,
MWMO_INPUTAVAILABLE | MWMO_ALERTABLE);
if (res == WAIT_FAILED)
{
running = FALSE;
hr = GetLastError();
break;
}
else if (res == WAIT_OBJECT_0 + 0)
{
CComBSTR err;
prescOverview->GetError(&err);
CString errContainer(err);
if (errContainer.GetLength() > 0)
{
// log
hr = E_FAIL;
}
else {
hr = S_OK;
}
running = FALSE;
break;
}
else if (res >= WAIT_OBJECT_0 && res <= WAIT_OBJECT_0 + handleCount)
{
// process messages.
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
PostQuitMessage(static_cast<int>(msg.wParam));
hr = ERROR_CANCELLED;
running = FALSE;
break;
}
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
} while (running);
}
CloseHandle(handle);
}
}
It's working. However The calling client must know how to do it -> read the docs and it's not intuitive.
I'm still working on it though.
Cheers,
Marco
I've just noticed i've never updated this.
Well, the easiest way to achieve this is to use events. If you are using ATL this will help you. I've managed this by first declaring the C# COM like this:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICSharpCom))]
[ComSourceInterfaces(typeof(ICSharpComEventHandler))]
[Guid("XXXXXX-xxxx-xxx-xxx-XXXXX")]
public class CSharpCom : ICSharpCom
{
[ComVisible(false)]
public delegate void WorkCompleted(string result);
public event WorkCompleted OnWorkCompleted;
public int DoWork(string input)
{
Task t = ....
// do some hard work aync by usin
OnWorkCompleted?.Invoke(t.Result);
}
}
[ComVisible(true)]
[Guid("XXXXXX-xxxx-yyy-xxx-XXXXX")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ICSharpComEventHandler
{
[DispId(1)]
void OnWorkCompleted(string result);
}
On The C++ side, it's not that easy
_ATL_FUNC_INFO OnWorkCompletedInfo = {CC_STDCALL, VT_EMPTY, 1, {VT_BSTR}};
class CEventSink : public IDispEventSimpleImpl<ONE_GOOD_ID, CEventSink, &DIID_ICSharpComEventHandler>
{
public:
BEGIN_SINK_MAP(CEventSink)
SINK_ENTRY_INFO(ONE_GOOD_ID, DIID_ICSharpComEventHandler, 1, OnWorkCompleted, &OnWorkCompletedInfo)
END_SINK_MAP()
const void __stdcall OnWorkCompleted(_bstr_t result)
{
// do something
}
CEventSink(CComPtr<ICSharpCom> psharp)
{
if (!pEps)
{
throw std::invalid_argument("psharp was null");
}
m_pSharp = psharp;
DispEventAdvise(m_pSharp);
}
void __stdcall StartListening()
{
DispEventAdvise(m_pSharp);
}
void __stdcall StopListening()
{
DispEventUnadvise(m_pSharp);
m_pSharp = nullptr;
}
private:
CComPtr<ICSharpCom> m_pSharp;
};
That's it guys.
Sorry for not posting before.
Cheers,
Marco
I have a tricky bug I can't find. I'm doing late-binding on from C# to a native DLL I wrote.
The late-binding seem to work fine. The problems started after I added the callbacks.
The callbacks are defined as so (in c)(On a global scale in the DLL):
typedef void (*FinishedDelegate) (ProcessResult a_bResult);
typedef void (*DownloadStatusUpdateDelegate) (int a_iParametersDownloaded, int a_iTotalParameters);
typedef void (*DownloadFinishedDelegate) (char* a_sConfiguration_values, ProcessResult a_bResult);
DownloadStatusUpdateDelegate pfDownloadStatusUpdateDelegate = NULL;
DownloadFinishedDelegate pfDownloadFinishedDelegate = NULL;
This function is exported:
PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate);
And this is the native function implementation:
DWORD WINAPI DownloadThreadFunc(void* a_pParam)
{
while (g_iParameterDownloaded < PARAMETER_COUNT)
{
if (IsEventSignaled(g_hAbortEvent))
{
CallDownloadFinished(PROCESS_ABORT);
return 0;
}
Sleep(STATUS_DELAY);
CallDownloadStatusUpdate();
g_iParameterDownloaded += STATUS_PARAMS_JUMP;
}
CallDownloadFinished(PROCESS_SUCCESS);
return 0;
}
PLUGIN_API BOOL DownloadConfigration(DownloadStatusUpdateDelegate a_pfStatusDelegate, DownloadFinishedDelegate a_pfFinishedDelegate)
{
if (IsEventSignaled(g_hInProcessEvent))
return false;
pfDownloadStatusUpdateDelegate = a_pfStatusDelegate;
pfDownloadFinishedDelegate = a_pfFinishedDelegate;
g_iParameterDownloaded = 0;
DWORD l_dwResult = WaitForSingleObject(g_hThreadsStructGuardian, INFINITE);
if (l_dwResult == WAIT_OBJECT_0)
{
g_ahThreads[PLUGIN_THREAD_DOWNLOAD] = CreateThread(NULL, 0, DownloadThreadFunc, 0, 0, NULL);
ReleaseMutex(g_hThreadsStructGuardian);
return true;
}
return false;
}
On the managed side, the function is called here:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void DownloadStatusUpdateDelegate(int a_iParametersDownloaded, int a_iTotalParameters);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void DownloadFinishedDelegate_Native(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult);
private void OnDownloadStatusUpdate(int a_iParametersDownloaded, int a_iTotalParameters)
{
if (DownloadStatusUpdate != null)
{
DownloadStatusUpdate(a_iParametersDownloaded, a_iTotalParameters);
}
}
private void OnDownloadFinished(StringBuilder a_sConfigurationValues, EPluginProcessResult a_eResult)
{
if (DownloadFinished != null)
{
DownloadFinished(a_sConfigurationValues.ToString(), a_eResult);
}
}
public bool DownloadConfiguration()
{
bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);
return l_bResult;
}
The weird thing is - It works for a while. I get a "Privileged Instruction" exception after some time, but as I lower STATUS_DELAY, it happens less. The exception shows up at the IsEventSignaled function - But there's simply nothing there.
The download thread syncs to the c# GUI thread to update the GUI.
I've been on this problem for way too much time. It looks like a classic calling conventions problem, but I verified it thoroughly!
Any ideas?
bool l_bResult = DLLDownloadConfigration(OnDownloadStatusUpdate, OnDownloadFinished);
The code isn't very clear, but this is the likely trouble spot. This creates two delegate objects, the callback bombs after the garbage collector runs and deletes the objects. It cannot track managed object references into unmanaged code. You'll need to create the delegates explicitly and store them in a class member so the garbage collector always sees at least one reference.
Have you tried using a lambda? As in:
bool l_bResult = DLLDownloadConfigration((downloaded, totalParams) => OnDownloadStatusUpdate(downloaded, totalParams), (values, result) => OnDownloadFinished(values, result));
My theory on this is that it is failing because your OnDownloadStatusUpdate and OnDownloadFinished methods aren't static. The underlying IL expects the 'this' object as the first invisible arg, but the C-method is not passing it when calling the callback.
EDIT: I think the answerer above me is correct, but can anyone shed some light on how the marshaller actually handles this?
What's a callback and how is it implemented in C#?
I just met you,
And this is crazy,
But here's my number (delegate),
So if something happens (event),
Call me, maybe (callback)?
In computer programming, a callback is executable code that is passed as an argument to other code.
—Wikipedia: Callback (computer science)
C# has delegates for that purpose. They are heavily used with events, as an event can automatically invoke a number of attached delegates (event handlers).
A callback is a function that will be called when a process is done executing a specific task.
The usage of a callback is usually in asynchronous logic.
To create a callback in C#, you need to store a function address inside a variable. This is achieved using a delegate or the new lambda semantic Func or Action.
public delegate void WorkCompletedCallBack(string result);
public void DoWork(WorkCompletedCallBack callback)
{
callback("Hello world");
}
public void Test()
{
WorkCompletedCallBack callback = TestCallBack; // Notice that I am referencing a method without its parameter
DoWork(callback);
}
public void TestCallBack(string result)
{
Console.WriteLine(result);
}
In today C#, this could be done using lambda like:
public void DoWork(Action<string> callback)
{
callback("Hello world");
}
public void Test()
{
DoWork((result) => Console.WriteLine(result));
DoWork(Console.WriteLine); // This also works
}
Definition
A callback is executable code that
is passed as an argument to other code.
Implementation
// Parent can Read
public class Parent
{
public string Read(){ /*reads here*/ };
}
// Child need Info
public class Child
{
private string information;
// declare a Delegate
delegate string GetInfo();
// use an instance of the declared Delegate
public GetInfo GetMeInformation;
public void ObtainInfo()
{
// Child will use the Parent capabilities via the Delegate
information = GetMeInformation();
}
}
Usage
Parent Peter = new Parent();
Child Johny = new Child();
// Tell Johny from where to obtain info
Johny.GetMeInformation = Peter.Read;
Johny.ObtainInfo(); // here Johny 'asks' Peter to read
Links
more details for C#.
A callback is a function pointer that you pass in to another function. The function you are calling will 'callback' (execute) the other function when it has completed.
Check out this link.
If you referring to ASP.Net callbacks:
In the default model for ASP.NET Web
pages, the user interacts with a page
and clicks a button or performs some
other action that results in a
postback. The page and its controls
are re-created, the page code runs on
the server, and a new version of the
page is rendered to the browser.
However, in some situations, it is
useful to run server code from the
client without performing a postback.
If the client script in the page is
maintaining some state information
(for example, local variable values),
posting the page and getting a new
copy of it destroys that state.
Additionally, page postbacks introduce
processing overhead that can decrease
performance and force the user to wait
for the page to be processed and
re-created.
To avoid losing client state and not
incur the processing overhead of a
server roundtrip, you can code an
ASP.NET Web page so that it can
perform client callbacks. In a client
callback, a client-script function
sends a request to an ASP.NET Web
page. The Web page runs a modified
version of its normal life cycle. The
page is initiated and its controls and
other members are created, and then a
specially marked method is invoked.
The method performs the processing
that you have coded and then returns a
value to the browser that can be read
by another client script function.
Throughout this process, the page is
live in the browser.
Source: http://msdn.microsoft.com/en-us/library/ms178208.aspx
If you are referring to callbacks in code:
Callbacks are often delegates to methods that are called when the specific operation has completed or performs a sub-action. You'll often find them in asynchronous operations. It is a programming principle that you can find in almost every coding language.
More info here: http://msdn.microsoft.com/en-us/library/ms173172.aspx
Dedication to LightStriker:
Sample Code:
class CallBackExample
{
public delegate void MyNumber();
public static void CallMeBack()
{
Console.WriteLine("He/She is calling you. Pick your phone!:)");
Console.Read();
}
public static void MetYourCrush(MyNumber number)
{
int j;
Console.WriteLine("is she/he interested 0/1?:");
var i = Console.ReadLine();
if (int.TryParse(i, out j))
{
var interested = (j == 0) ? false : true;
if (interested)//event
{
//call his/her number
number();
}
else
{
Console.WriteLine("Nothing happened! :(");
Console.Read();
}
}
}
static void Main(string[] args)
{
MyNumber number = Program.CallMeBack;
Console.WriteLine("You have just met your crush and given your number");
MetYourCrush(number);
Console.Read();
Console.Read();
}
}
Code Explanation:
I created the code to implement the funny explanation provided by LightStriker in the above one of the replies. We are passing delegate (number) to a method (MetYourCrush). If the Interested (event) occurs in the method (MetYourCrush) then it will call the delegate (number) which was holding the reference of CallMeBack method. So, the CallMeBack method will be called. Basically, we are passing delegate to call the callback method.
Please let me know if you have any questions.
Probably not the dictionary definition, but a callback usually refers to a function, which is external to a particular object, being stored and then called upon a specific event.
An example might be when a UI button is created, it stores a reference to a function which performs an action. The action is handled by a different part of the code but when the button is pressed, the callback is called and this invokes the action to perform.
C#, rather than use the term 'callback' uses 'events' and 'delegates' and you can find out more about delegates here.
callback work steps:
1) we have to implement ICallbackEventHandler Interface
2) Register the client script :
String cbReference = Page.ClientScript.GetCallbackEventReference(this, "arg", "ReceiveServerData", "context");
String callbackScript = "function UseCallBack(arg, context)" + "{ " + cbReference + ";}";
Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "UseCallBack", callbackScript, true);
1) from UI call Onclient click call javascript function for EX:- builpopup(p1,p2,p3...)
var finalfield= p1,p2,p3;
UseCallBack(finalfield, ""); data from the client passed to server side by using UseCallBack
2) public void RaiseCallbackEvent(string eventArgument) In eventArgument we get the passed data
//do some server side operation and passed to "callbackResult"
3) GetCallbackResult() // using this method data will be passed to client(ReceiveServerData() function) side
callbackResult
4) Get the data at client side:
ReceiveServerData(text) , in text server response , we wil get.
A callback is a function passed as an argument to another function. This technique allows a function to invoke the parameter function argument and even to pass a value back to the caller. A callback function can be designed to run before/after the function has finished and can pass a value.
It is a kind of construct where you call a long running function and ask him to call you back once it has finished with can return a parameter result to the caller.
It's like someone calls you in the middle of your work asking for status and you say "you know what give me 5 min and i will call you back" and at the end you call him to update. If you are a function the caller just added and passed another function that you invoked at the end. This can simpley be written in C# as:
public void VinodSrivastav(Action statusUpdate){
//i am still here working..working
//i have finished, calling you
statusUpdate();
}
//invokes
stackoverflow.VinodSrivastav((cam) => {
Console.Write("Is it finished");
});
The one simple example is the iterator function where the return will be multiple times, one can argue that we have yield for it:
public void IntreationLoop(int min, int max,Action<int> Callback)
{
for(int i = min;i<= max;i++)
Callback(i);
}
//call
IntreationLoop(5,50,(x) => { Console.Write(x); }); //will print 5-50 numbers
In the code above the function return type is void but it has an Action<int> callback which is called and sends each item from the loop to the caller.
The same thing can be done with if..else or try..catch block as:
public void TryCatch(Action tryFor,Action catchIt)
{
try{
tryFor();
}
catch(Exception ex)
{
Console.WriteLine($"[{ex.HResult}] {ex.Message}");
catchIt();
}
}
And call it as:
TryCatch(()=>{
int r = 44;
Console.WriteLine("Throwing Exception");
throw new Exception("something is wrong here");
}, ()=>{
Console.WriteLine("It was a mistake, will not try again");
});
In 2022 we have Func & Action doing the same, please see the demo code below which shows how this can be be used:
void Main()
{
var demo = new CallbackDemo();
demo.DoWork(()=> { Console.WriteLine("I have finished the work"); });
demo.DoWork((r)=> { Console.WriteLine($"I have finished the work here is the result {r}"); });
demo.DoWork(()=> { Console.WriteLine($"This is passed with func"); return 5;});
demo.DoWork((f)=> { Console.WriteLine($"This is passed with func and result is {f}"); return 10;});
}
// Define other methods and classes here
public class CallbackDemo
{
public void DoWork(Action actionNoParameter)
{
int a = 5;
int b = 10;
//i will do th maths and call you back
int result = a + b;
//callback
actionNoParameter(); //execute
Console.WriteLine($"[The Actual Result is {result}]");
}
public void DoWork(Action<int> actionWithParameter)
{
int a = 5;
int b = 10;
//i will do th maths and call you back
int result = a + b;
//callback
actionWithParameter(result); //execute
Console.WriteLine($"[The Actual Result is {result}]");
}
public void DoWork(Func<int> funcWithReturn)
{
int a = 5;
int b = 10;
//i will do th maths and call you back
int result = a + b;
//callback
int c = funcWithReturn(); //execute
result += c;
Console.WriteLine($"[The Actual Result is {result}]");
}
public void DoWork(Func<int,int> funcWithParameter)
{
int a = 5;
int b = 10;
//i will do th maths and call you back
int result = a + b;
//callback
result += funcWithParameter(result); //execute
Console.WriteLine($"[The Actual Result is {result}]");
}
}