PDA

View Full Version : بازگرداندن مقدار null با استفاده از چند نخی



kingtak
جمعه 06 آذر 1394, 23:24 عصر
سلام دوستان
من دارم یه برنامه مینویسم که باید چند IP رو به صورت همزمان پینگ کنه و نتیجه رو در جدول نشون بده.
برای اینکار دوتا کلاس دارم.یکیش کلاس اصلی منه و دومی فقط یک آی پی رو میگیره و نتیجه پینگ رو نشون میده.ولی نمیدونم مشکل از کجاست که مقدار null بر میگردونه.داخل خود کلاس درست پرینت میگیره ولی وقتی به کلاس اصلی بر میگردونه مقدار نال تحویل میده.فکر کنم مشکل من توی چند نخی هستش ولی نتونستم مشکل رو حل کنم.
این هم کد کلاس اصلی:


public class MainPageController implements Initializable {


@FXML
private TableView tbl_result;
@FXML
private TableColumn tbl_col_result;
@FXML
private ObservableList<String> data = FXCollections.observableArrayList();


@Override
public void initialize(URL url, ResourceBundle rb) {
startPing();
}


public void startPing() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream file = classLoader.getResourceAsStream("ip-list.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(file));
String line;
try {
while ((line = br.readLine()) != null) {
MultiplePinger mp;
new Thread(mp = new MultiplePinger(line)).start();
addToList(mp.getResult());
}
printList();
} catch (IOException ex) {
Logger.getLogger(MultiplePinger.class.getName()).l og(Level.SEVERE, null, ex);
}
}


public void printList() {
for (String item : data) {
System.out.println(item);
}
}


public void addToList(String result) {
data.add(result);
}
}




و این هم کد کلاس دومی که پینگ رو انجام میده:


public class MultiplePinger implements Runnable {


private String addr,result;


public MultiplePinger(String address) {
addr = address;
}


@Override
public void run() {


StringBuffer buf = new StringBuffer(String.format("%-55s", addr) + "\t");
String s;
Process process;
try {
process = Runtime.getRuntime().exec("cmd /c " + "ping " + addr);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));


while ((s = br.readLine()) != null) {
if (s.indexOf("Average") != -1) {
buf.append(s);
}
}


process.waitFor();
result=buf.toString();

} catch (Exception ex) {
ex.printStackTrace();
}
}


public String getResult() {
return result;
}
}

ahmad.mo74
شنبه 07 آذر 1394, 20:42 عصر
سلام.

اشکال کارت تو این خطه :

addToList(mp.getResult());

بخاطر اینکه هنوز شما نمیدونی کار اون ترد تموم شده یا نه، اومدی result رو ازش گرفتی و ریختی توی data. که طبیعتا باید null باشه.
باید اول صبر کنی تا کار متد run توی کلاس MultiplePinger تموم بشه و جواب تولید بشه بعد اون رو به data اضافه کنی.

برای اینکه درست کار کنه خیلی کارا میشه کرد، مثلا listener تعریف کنی و هر وقت result آماده شد به data اضافه بشه.

kingtak
شنبه 07 آذر 1394, 23:25 عصر
سلام.

اشکال کارت تو این خطه :

addToList(mp.getResult());


برای اینکه درست کار کنه خیلی کارا میشه کرد، مثلا listener تعریف کنی و هر وقت result آماده شد به data اضافه بشه.

میشه کمکم کنید؟چطور باید این لیستنرlistener رو تعریف کنم؟

ahmad.mo74
یک شنبه 08 آذر 1394, 12:26 عصر
اول باید یه interface تعریف کنیم :


public interface PingListener {


void onResult(String result);


}


متد onResult زمانی باید صدا زده بشه که result آماده شده و کسی که این interface رو پیاده سازی کرده با خبر بشه.

کد کلاس MultiplePinger باید اینطوری تغییر کنه :


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;


public class MultiplePinger implements Runnable {


private String addr;
private String result;


private final List<PingListener> pingListeners = new CopyOnWriteArrayList<>();


public MultiplePinger(String address) {
addr = address;
}


@Override
public void run() {




StringBuilder sb = new StringBuilder(String.format("%-55s", addr) + "\t");
String s;
Process process;
try {
process = Runtime.getRuntime().exec("cmd /c " + "ping " + addr);
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));




while ((s = br.readLine()) != null) {
if (s.contains("Average")) {
sb.append(s);
}
}




process.waitFor();
fireOnResult(result = sb.toString());


} catch (Exception ex) {
ex.printStackTrace();
}
}


public String getResult() {
return result;
}


public void addPingListener(PingListener listener) {
pingListeners.add(listener);
}


public void removePingListener(PingListener listener) {
pingListeners.remove(listener);
}


private void fireOnResult(String result) {
for (PingListener listener : pingListeners) {
listener.onResult(result);
}
}


}


ما اینجا یه لیست از PingListener تعریف کردیم و موقعی که result آماده میشه به تک تک شون میفرستیم.

کسی که باید PingListener رو پیاده سازی کنه، کلاس MainPageController هست :


import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ResourceBundle;


public class MainPageController implements Initializable, PingListener {


@FXML
private TableView<String> tbl_result;
@FXML
private TableColumn<String, String> tbl_col_result;


private ObservableList<String> data = FXCollections.observableArrayList();


@Override
public void initialize(URL url, ResourceBundle rb) {
tbl_col_result.setCellValueFactory(param -> new SimpleStringProperty(param.getValue()));
tbl_result.setItems(data);
startPing();
}


public void startPing() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("ip-list.txt")))) {
String line;
while ((line = br.readLine()) != null) {
MultiplePinger mp = new MultiplePinger(line);
mp.addPingListener(this);
new Thread(mp).start();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}


@Override
public void onResult(String result) {
System.out.println(result);
Platform.runLater(() -> data.add(result));
}


}


موقع ساختن MultiplePinger، ما MainPageController رو به لیست PingListener هاش اضافه می کنیم و بعد ترد رو استارت می کنیم.
توی متد onResult هم گفتیم که هر وقت result اومد به data اضافه اش کن.

استفاده از Platform.runLater هم فقط برای اطمینان که مطمئن باشیم از تو تردی که برنامه باهاش ران شده آپدیت روی تیبل انجام میشه، در غیر اینصورت اکسپشن رخ میده.

-سیّد-
دوشنبه 09 آذر 1394, 10:31 صبح
ضمن تشکر از جواب دوستمون، راه بهتر و استانداردترش اینه که به جای Runnable از Callable استفاده کنید، و برای اجرای چند نخی، از ExecutorService استفاده کنید.

ahmad.mo74
دوشنبه 09 آذر 1394, 16:55 عصر
سلام



ضمن تشکر از جواب دوستمون


ممنون.



راه بهتر و استانداردترش اینه که به جای Runnable از Callable استفاده کنید، و برای اجرای چند نخی، از ExecutorService استفاده کنید.


بله، آقا سید کاملا درست میگن.
سعی کنید برای پیاده سازی برنامه های multi-threaded تا جایی که میشه مستقیما خودتون از Thread استفاده نکنید و این کار رو به فریم ورک های عالی ای که تو لایبرری جاوا موجود هست بسپارید.
تا جایی که می تونید از کلاس های پکیج java.util.concurrent استفاده کنید.

البته همراه با Java 8 کلاس java.util.concurrent.CompletableFuture اومد که خیلی کلاس به درد بخوری هست برای اینجور کارها. برای اجرا کردن تردها از ForkJoinPool که یه ExecutorService هست استفاده میکنه.

اگر تو این برنامه بخوایم ازش استفاده کنیم، اول اینکه کلاس MultiplePinger (که با اجازت اسمشو به PingProcess تغییر میدم) به جای Runnable باید Callable (یا Supplier) رو پیاده سازی کنه :


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.function.Supplier;


public class PingProcess implements Supplier<String> {


private String address;


public PingProcess(String address) {
this.address = address;
}


@Override
public String get() {
StringBuilder sb = new StringBuilder(String.format("%-55s", address) + "\t");
try {
Process process = Runtime.getRuntime().exec("cmd /c " + "ping " + address);
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
if (line.contains("Average")) {
sb.append(line);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}


}


و دیگه نیازی به listener هم نیست و کلاس MainPageController هم نیازی به پیاده سازی PingListener نداره.


import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ResourceBundle;
import java.util.concurrent.CompletableFuture;


public class MainPageController implements Initializable {


@FXML
private TableView<String> tbl_result;
@FXML
private TableColumn<String, String> tbl_col_result;


private ObservableList<String> data = FXCollections.observableArrayList();


@Override
public void initialize(URL url, ResourceBundle rb) {
tbl_col_result.setCellValueFactory(param -> new SimpleStringProperty(param.getValue()));
tbl_result.setItems(data);
startPing();
}


private void startPing() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("ip-list.txt")))) {
String address;
while ((address = br.readLine()) != null) {
CompletableFuture.supplyAsync(new PingProcess(address)).thenAccept(this::addResult);
}
} catch (IOException ex) {
ex.printStackTrace();
}
}


private void addResult(String result) {
System.out.println(result);
Platform.runLater(() -> data.add(result));
}


}


به جاش، برای هر آدرس یه PingProcess نیو می کنیم، میدیمش به CompletableFuture تا اجراش کنه (متد supplyAsync) و بعد هم ازش میخوایم موقعی که جواب رو گرفت متد addResult رو صدا کنه و result رو براش بفرسته (متد thenAccept).
اینطوری کدمون خیلی قشنگ تر و کوچیک تر هم میشه.

متدهای دیگه کلاس CompletableFuture رو هم ببینید.

kingtak
چهارشنبه 11 آذر 1394, 20:16 عصر
دوستان عزیز بابت راهنمایی تون خیلی ممنونم.....واقعا کامل توضیح دادید....
من باید در مورد چند نخی و این فریمورک ها مطالعه کنم چون اطلاعاتم در این مورد خیلی کمه....
قراره این برنامه رو من روی ویندوز XP اجرا کنم که فکر کنم با جاوا 8 مشکل داره.ولی باز در این مورد تحقیق میکنم.
توی نت زیاد دنبال کتاب چند نخی گشتم ولی همشون خیلی قدیمی هستن....اگه شما کتابی دارید ممنون میشم معرفی کنید.
بازهم از زحماتتون تشکر میکنم.

ahmad.mo74
جمعه 13 آذر 1394, 21:39 عصر
قراره این برنامه رو من روی ویندوز XP اجرا کنم که فکر کنم با جاوا 8 مشکل داره.

مگه میشه؟؟؟
چرا باید با Java 8 مشکل داشته باشه؟ نشنیده بودم همچین چیزی...

kingtak
شنبه 14 آذر 1394, 00:45 صبح
مگه میشه؟؟؟
چرا باید با Java 8 مشکل داشته باشه؟ نشنیده بودم همچین چیزی...

البته باید میگفتم جاوا 8 با ویندوز XP مشکل داره ...
من خودم روی ویندوز ایکس پی نصب کردم و ارور داد سیستم عاملتو به ویستا یا 7 ارتقا بده.هنگام کامپایل روی ویندوز ایکس پی گفت شما جاوا نصب نکردید.تحقیق کردم دیدم جاوا 8 ویندوز ایکس پی رو ساپرت نمیکنه.
توی این لینک هم به این موضوع اشاره کرده:
https://adtmag.com/articles/2014/07/15/xp-and-java.aspx
و این لینک:

http://fanyit.com/problems-solutions-it/cant-install-java/

و این لینک:

http://www.zdnet.com/article/java-support-over-for-windows-xp/

ahmad.mo74
یک شنبه 15 آذر 1394, 09:31 صبح
جالبه... یعنی خیلی عجیبه!

پس شما باید از Java 7 استفاده کنی. یعنی از همون ExecutorService و اینترفیس Callable. ولی یه خورده باید تغییرات داده بشه توی کد.
توی اینترفیس ExecutorService وقتی یک Callable رو بهش میدی تا اجرا بکنه، یه Future برمیگردونه که بعدا میتونیم از طریق این آبجکت Future از وضعیت تسکمون با خبر بشیم و در صورت نیاز خروجیش رو بگیریم.
برای اینکار ما باید یه خورده کار اضافه انجام بدیم. اول یه اینترفیس به اسم FutureCallback میسازیم :


public interface FutureCallback<V> {


void onSuccess(V result);


void onFailure(Throwable cause);


}


متد onSuccess برای وقتیه که تسک به درستی انجام شده و خروجی رو هم گرفتیم ازش.
متد onFailure هم برای زمانی که تسک به مشکل خورده و موقع اجرا اکسپشن داشتیم.

خب ما اینجا یه helper class درست میکنیم به اسم Futures که کارش این باشه که بهش Callable بدیم بعد اونو اجرا کنه و خروجی بگیره بده به FutureCallback.
یا اینکه بهش Future بدیم، منتظر اتمام تسک بمونه بعدش خروجی رو بگیره و بازم بده به FutureCallback.


import java.util.concurrent.*;


/**
* @author ahmad
*/
public final class Futures {


private static final ThreadFactory DEFAULT_THREAD_FACTORY = Executors.defaultThreadFactory();
private static final ExecutorService COMMON_EXECUTOR_SERVICE = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = DEFAULT_THREAD_FACTORY.newThread(r);
t.setDaemon(true);
return t;
}
});


public static <V> void addCallback(final Callable<V> callable, final FutureCallback<V> callback) {
addCallback(COMMON_EXECUTOR_SERVICE, callable, callback);
}


public static <V> void addCallback(final ExecutorService executorService, final Callable<V> callable, final FutureCallback<V> callback) {
addCallback(executorService, executorService.submit(callable), callback);
}


public static <V> void addCallback(final Future<V> future, final FutureCallback<V> callback) {
addCallback(COMMON_EXECUTOR_SERVICE, future, callback);
}


public static <V> void addCallback(final ExecutorService executorService, final Future<V> future, final FutureCallback<V> callback) {
executorService.execute(new CompletionTask<>(future, callback));
}


private static final class CompletionTask<V> implements Runnable {


private final Future<V> future;
private final FutureCallback<V> callback;


private CompletionTask(Future<V> future, FutureCallback<V> callback) {
this.future = future;
this.callback = callback;
}


@Override
public void run() {
V result = null;
Throwable cause = null;
try {
result = future.get();
} catch (Throwable t) {
cause = t;
}
try {
if (cause == null) {
callback.onSuccess(result);
} else {
callback.onFailure(cause);
}
} catch (Throwable t) {
throw new Error(t);
}
}


}


}


COMMON_EXECUTOR_SERVICE برای زمانیه که کسی که از این کلاس استفاده میکنه نخواد خودش یه ExecutorService جدید درست کنه.
اگه به متد Executors.newCachedThreadPool نگاه کنی میبینی که دقیقا همینه فقط من اومدم ThreadFactory اش رو عوض کردم و موقع ساخت ترد جدید گفتم که daemon باشه.

PingProcess هم هیچی تغییری نداشته فقط به جای Supplier باید Callable رو پیاده سازی کنه :


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.Callable;


public class PingProcess implements Callable<String> {


private String address;


public PingProcess(String address) {
this.address = address;
}


@Override
public String call() throws Exception {
StringBuilder sb = new StringBuilder(String.format("%-55s", address) + "\t");
try {
Process process = Runtime.getRuntime().exec("cmd /c " + "ping " + address);
try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = br.readLine()) != null) {
if (line.contains("Average")) {
sb.append(line);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}


}


کد کلاس MainPageController هم خیلی تغییری نکرده. فقط به جای CompletableFuture از کلاسی که خودمون ساختیم استفاده کردیم (در واقع CompletableFuture هم اون زیر تقریبا یه همچین کاری رو برامون انجام میده) و اینکه دیگه خبری از lambda نیست و یه خورده کد بزرگتر شده.


import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.util.Callback;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ResourceBundle;


public class MainPageController implements Initializable {


@FXML
private TableView<String> tbl_result;
@FXML
private TableColumn<String, String> tbl_col_result;


private final ObservableList<String> data = FXCollections.observableArrayList();


@Override
public void initialize(URL url, ResourceBundle rb) {
tbl_col_result.setCellValueFactory(new Callback<TableColumn.CellDataFeatures<String, String>, ObservableValue<String>>() {
@Override
public ObservableValue<String> call(TableColumn.CellDataFeatures<String, String> data) {
return new SimpleStringProperty(data.getValue());
}
});
tbl_result.setItems(data);
startPing();
}


private void startPing() {
try (BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("ip-list.txt")))) {
String address;
while ((address = br.readLine()) != null) {
Futures.addCallback(new PingProcess(address), new FutureCallback<String>() {
@Override
public void onSuccess(String result) {
addResult(result);
}


@Override
public void onFailure(Throwable cause) {
cause.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}


private void addResult(final String result) {
System.out.println(result);
Platform.runLater(new Runnable() {
@Override
public void run() {
data.add(result);
}
});
}


}

-سیّد-
یک شنبه 15 آذر 1394, 17:34 عصر
باز هم ضمن تشکر، این رو هم اضافه کنم که کتابخونه‌ی guava (https://en.wikipedia.org/wiki/Google_Guava) که مال گوگل هست، امکانات خیلی خوبی برای کار به صورت چند ریسمانی در اختیار شما قرار می‌ده. می‌تونید با استفاده از اون خیلی از کارهایی که اینجا بهشون اشاره شده رو انجام بدید.